· 5 years ago · May 31, 2020, 09:58 AM
1{
2 mainFile = false,
3 compressed = false,
4 data = {
5 [ "games/etc/apps.db" ] = "{\
6 [ \"462e1d985e9adb99f4bbb4455d99329900450224\" ] = {\
7 title = \"SameGame\",\
8 category = \"Games\",\
9 run = \"sameGame\",\
10 iconExt = \"\\132\\132\\132\\132\\132\\132\\132\\132\\010\\030b\\031e\\148\\030a\\031d\\139\\030b\\031a\\154\\030c\\031b\\139\\030e\\031c\\138\\030d\\140\\030e\\031a\\139\\030f\\031b\\149\\010\\030a\\031e\\145\\030e\\031d\\143\\130\\030c\\149\\030b\\031e\\156\\030d\\031b\\149\\030a\\031d\\132\\030f\\031e\\149\",\
11 },\
12 [ \"f6a5201214fb1981e6d46a39303bb676325eb59b-ME2\" ] = {\
13 title = \"Othello\",\
14 category = \"Games\",\
15 run = \"Othello\",\
16 iconExt = \"\\030 \\031 \\128\\030d\\151\\031d\\128\\031f\\144\\031 \\131\\131\\143\\143\\010\\030d\\031 \\151\\0300\\031d\\159\\030d\\031f\\130\\0300\\031d\\153\\030f\\139\\030d\\0310\\132\\031f\\132\\030 \\031d\\133\\010\\030 \\031d\\131\\131\\143\\143\\030d\\031f\\129\\031d\\128\\030 \\133\\031 \\128\",\
17 },\
18 [ \"785af2a4ad3c4ee912623c6e0b6d4299ea305bf6\" ] = {\
19 title = \"Pipes\",\
20 category = \"Games\",\
21 run = \"Pipes\",\
22 iconExt = \"\\030 \\031 \\128\\0300\\0310\\128\\030 \\031 \\128\\0310\\143\\0300\\031 \\130\\030 \\128\\0300\\0310\\128\\010\\0300\\031 \\131\\0310\\128\\031 \\131\\131\\030 \\0310\\159\\031 \\128\\0310\\143\\010\\030 \\031 \\128\\0315\\143\\031 \\128\\128\\128\\128\\0300\\131\",\
23 },\
24 [ \"a2accffe95b2c8be30e8a05e0c6ab7e8f5966f43\" ] = {\
25 title = \"Strafe\",\
26 category = \"Games\",\
27 icon = \"\\0308\\031f \\0300 \\0308 \\010\\0308\\031f \\0300 \\030f \\010\\0300\\031f \\030f \",\
28 iconExt = \"\\0308\\0318\\128\\0300\\159\\129\\0310\\128\\0308\\159\\129\\0318\\128\\010\\0300\\0318\\135\\0310\\128\\128\\030f\\135\\0300\\031f\\143\\159\\030f\\0310\\144\\010\\0300\\128\\030f\\159\\129\\138\\0300\\031f\\143\\149\\030f\\0310\\134\",\
29 run = \"Strafe\",\
30 },\
31 [ \"48d6857f6b2869d031f463b13aa34df47e18c548\" ] = {\
32 title = \"Breakout\",\
33 category = \"Games\",\
34 icon = \"\\0301\\031f \\0309 \\030c \\030b \\030e \\030c \\0306 \\010\\030 \\031f \\010\\030 \\031f \\0300 \\0310 \",\
35 iconExt = \"\\030 \\0319\\144\\030d\\031 \\159\\030b\\159\\030 \\0311\\144\\031b\\144\\030c\\031 \\159\\030 \\0311\\144\\010\\030 \\0311\\130\\031b\\129\\0319\\130\\031e\\130\\0310\\144\\031d\\129\\0316\\129\\010\\030 \\136\\140\\140\\031 \\128\\128\\128\\128\",\
36 run = \"Breakout\",\
37 },\
38 [ \"53a5d150062b1e03206b9e15854b81060e3c7552\" ] = {\
39 title = \"Minesweeper\",\
40 category = \"Games\",\
41 icon = \"\\030f\\031f \\03131\\0308\\031f \\030f\\031d2\\010\\030f\\031f \\031d2\\03131\\0308\\031f \\030f\\03131\\010\\030f\\03131\\0308\\031f \\030f\\03131\\031e3\",\
42 run = \"Minesweeper\",\
43 },\
44 [ \"4e404681d4fa9e04ae2bb63d82f58a9733fa605a\" ] = {\
45 title = \"Tron\",\
46 category = \"Games\",\
47 iconExt = \"\\030 \\031f\\030b\\031f\\143\\030f\\128\\128\\030b\\143\\143\\143\\030f\\128\\128\\010\\030 \\031f\\0309\\031b\\140\\030b\\031f\\151\\030f\\031b\\131\\0307\\148\\0317\\128\\030b\\151\\030f\\031b\\131\\148\\010\\030 \\031f\\030f\\031b\\131\\031f\\128\\031b\\131\\0317\\131\\031f\\128\\0317\\131\\031b\\131\\031f\\128\",\
48 run = \"Tron\",\
49 },\
50}",
51 [ "common/etc/scripts/summon" ] = "local function summon(id)\
52 local GPS = require('opus.gps')\
53 local Point = require('opus.point')\
54 local Socket = require('opus.socket')\
55\
56 turtle.setStatus('GPSing')\
57 turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })\
58\
59 local pts = {\
60 [ 1 ] = { x = 0, z = 0, y = 0 },\
61 [ 2 ] = { x = 4, z = 0, y = 0 },\
62 [ 3 ] = { x = 2, z = -2, y = 2 },\
63 [ 4 ] = { x = 2, z = 2, y = 2 },\
64 }\
65 local tFixes = { }\
66\
67 local socket = Socket.connect(id, 161)\
68\
69 if not socket then\
70 error('turtle: Unable to connect to ' .. id)\
71 end\
72\
73 local function getDistance()\
74 socket:write({ type = 'ping' })\
75 local _, d = socket:read(5)\
76 return d\
77 end\
78\
79 local function doGPS()\
80 tFixes = { }\
81 for i = 1, 4 do\
82 if not turtle.go(pts[i]) then\
83 error('turtle: Unable to perform GPS maneuver')\
84 end\
85 local distance = getDistance()\
86 if not distance then\
87 error('turtle: No response from ' .. id)\
88 end\
89 table.insert(tFixes, {\
90 position = vector.new(turtle.point.x, turtle.point.y, turtle.point.z),\
91 distance = distance\
92 })\
93 end\
94 return true\
95 end\
96\
97 if not doGPS() then\
98 turtle.turnAround()\
99 turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0})\
100 if not doGPS() then\
101 socket:close()\
102 return false\
103 end\
104 end\
105\
106 socket:close()\
107\
108 local pos = GPS.trilaterate(tFixes)\
109\
110 if pos then\
111 local pt = { x = pos.x, y = pos.y, z = pos.z }\
112 local _, h = Point.calculateMoves(turtle.getPoint(), pt)\
113 local hi = turtle.getHeadingInfo(h)\
114 turtle.setStatus('recalling')\
115 turtle.pathfind({ x = pt.x - hi.xd, z = pt.z - hi.zd, y = pt.y - hi.yd, heading = h })\
116 else\
117 error(\"turtle: Could not determine position\")\
118 end\
119end\
120\
121turtle.run(function() summon({COMPUTER_ID}) end)",
122 [ "common/DiskCopy.lua" ] = "local Ansi = require('opus.ansi')\
123local Config = require('opus.config')\
124local Event = require('opus.event')\
125local UI = require('opus.ui')\
126local Util = require('opus.util')\
127\
128local colors = _G.colors\
129local fs = _G.fs\
130local peripheral = _G.peripheral\
131\
132local drives = { }\
133if peripheral.getType('left') == 'drive' then\
134 drives.left = Util.shallowCopy(peripheral.wrap('left'))\
135 drives.left.name = 'left'\
136end\
137if peripheral.getType('right') == 'drive' then\
138 drives.right = Util.shallowCopy(peripheral.wrap('right'))\
139 drives.right.name = 'right'\
140end\
141\
142peripheral.find('drive', function(n, v)\
143 if not drives.left then\
144 drives.left = Util.shallowCopy(v)\
145 drives.left.name = n\
146 elseif not drives.right then\
147 drives.right = Util.shallowCopy(v)\
148 drives.right.name = n\
149 end\
150end)\
151\
152if not (drives.left and drives.right) then\
153 error('Two drives are required')\
154end\
155\
156local COPY_LEFT = 1\
157local COPY_RIGHT = 2\
158local directions = {\
159 [ COPY_LEFT ] = { text = '-->>' },\
160 [ COPY_RIGHT ] = { text = '<<--' },\
161}\
162\
163local config = Config.load('DiskCopy', {\
164 eject = true,\
165 automatic = false,\
166 copyDir = COPY_LEFT\
167})\
168\
169local page = UI.Page {\
170 linfo = UI.Window {\
171 x = 2, y = 2, ey = 5, width = 18,\
172 },\
173 rinfo = UI.Window {\
174 x = -19, y = 2, ey = 5, width = 18,\
175 },\
176 dir = UI.Button {\
177 x = 17, y = 6, width = 6,\
178 event = 'change_dir',\
179 },\
180 progress = UI.ProgressBar {\
181 x = 2, ex = -2, y = -4,\
182 backgroundColor = colors.black,\
183 },\
184 ejectText = UI.Text {\
185 x = 2, y = -2,\
186 value = 'Eject'\
187 },\
188 eject = UI.Checkbox {\
189 x = 8, y = -2,\
190 },\
191 automaticText = UI.Text {\
192 x = 12, y = -2,\
193 value = 'Copy automatically'\
194 },\
195 automatic = UI.Checkbox {\
196 x = 31, y = -2,\
197 },\
198 copyButton = UI.Button {\
199 x = -7, y = -2,\
200 text = 'Copy',\
201 event = 'copy',\
202 inactive = true,\
203 },\
204 warning = UI.Text {\
205 x = 2, ex = -2, y = -1,\
206 align = 'center',\
207 textColor = colors.orange,\
208 },\
209 notification = UI.Notification { },\
210}\
211\
212function page:enable()\
213 Util.merge(self.dir, directions[config.copyDir])\
214\
215 self.eject.value = config.eject\
216 self.automatic.value = config.automatic\
217\
218 self.dir:move(math.floor((self.width / 2) - 3) + 1, self.dir.y)\
219\
220 UI.Page.enable(self)\
221end\
222\
223local function isValid(drive)\
224 return drive.isDiskPresent() and drive.getMountPath()\
225end\
226\
227local function needsLabel(drive)\
228 return drive.isDiskPresent() and not drive.getMountPath() and not drive.getAudioTitle()\
229end\
230\
231function page:drawInfo(drive, textArea)\
232 local function getLabel()\
233 return not drive.isDiskPresent() and 'empty' or\
234 not drive.getMountPath() and 'invalid' or\
235 drive.getDiskLabel() or 'unlabeled'\
236 end\
237\
238 local function getUsed()\
239 return isValid(drive) and fs.getSize(drive.getMountPath(), true) or 0\
240 end\
241\
242 local function getFree()\
243 return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0\
244 end\
245\
246 textArea:print(string.format('Drive: %s%s%s\\nLabel: %s%s%s\\nUsed: %s%s%s\\nFree: %s%s%s',\
247 Ansi.yellow, drive.name, Ansi.reset,\
248 isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,\
249 Ansi.yellow, Util.toBytes(getUsed()), Ansi.reset,\
250 Ansi.yellow, Util.toBytes(getFree()), Ansi.reset))\
251end\
252\
253function page:scan()\
254 local showWarning = needsLabel(drives.left) or needsLabel(drives.right)\
255 local valid = isValid(drives.left) and isValid(drives.right)\
256\
257 self.warning.value = showWarning and 'Computers must be labeled'\
258 self.copyButton.inactive = not valid\
259\
260 self:draw()\
261 self.progress:clear()\
262 self.progress:centeredWrite(1, 'Analyzing Disks..')\
263 self.progress:sync()\
264\
265 self:drawInfo(drives.left, self.linfo)\
266 self:drawInfo(drives.right, self.rinfo)\
267\
268 self.progress:clear()\
269end\
270\
271function page:copy()\
272 local sdrive = config.copyDir == COPY_LEFT and drives.left or drives.right\
273 local tdrive = config.copyDir == COPY_LEFT and drives.right or drives.left\
274\
275 local throttle = Util.throttle()\
276 local sourceFiles, targetFiles = { }, { }\
277\
278 local function getListing(mountPath, path, files)\
279 for _,f in pairs(fs.list(path)) do\
280 local file = fs.combine(path, f)\
281 if not fs.isReadOnly(file) then\
282 files[string.sub(file, #mountPath + 1)] = true\
283 if fs.isDir(file) then\
284 getListing(mountPath, file, files)\
285 end\
286 end\
287 end\
288 throttle()\
289 end\
290\
291 self.progress:clear()\
292 self.progress:centeredWrite(1, 'Computing..')\
293 self.progress:sync()\
294\
295 getListing(sdrive.getMountPath(), sdrive.getMountPath(), sourceFiles)\
296 getListing(tdrive.getMountPath(), tdrive.getMountPath(), targetFiles)\
297\
298 local copied = 0\
299 local totalFiles = Util.size(sourceFiles)\
300\
301 local function rawCopy(source, target)\
302 if fs.isDir(source) then\
303 copied = copied + 1\
304 if not fs.exists(target) then\
305 fs.makeDir(target)\
306 end\
307 for _,f in pairs(fs.list(source)) do\
308 rawCopy(fs.combine(source, f), fs.combine(target, f))\
309 end\
310\
311 else\
312 if fs.exists(target) then\
313 fs.delete(target)\
314 end\
315\
316 fs.copy(source, target)\
317 copied = copied + 1\
318 self.progress.value = copied * 100 / totalFiles\
319 self.progress:draw()\
320 self.progress:sync()\
321 end\
322 throttle()\
323 end\
324\
325 local function cleanup()\
326 for k in pairs(targetFiles) do\
327 if not sourceFiles[k] then\
328 fs.delete(fs.combine(tdrive.getMountPath(), k))\
329 end\
330 end\
331 end\
332\
333 self.progress:clear()\
334 rawCopy(sdrive.getMountPath(), tdrive.getMountPath())\
335 cleanup()\
336\
337 self.progress:clear()\
338 self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)\
339 self.progress:sync()\
340\
341 self.progress.value = 0\
342-- self.progress:clear()\
343\
344 self:scan()\
345\
346 if config.eject then\
347 tdrive.ejectDisk()\
348 end\
349end\
350\
351function page:eventHandler(event)\
352 if event.type == 'change_dir' then\
353 config.copyDir = (config.copyDir) % 2 + 1\
354 Util.merge(self.dir, directions[config.copyDir])\
355 Config.update('DiskCopy', config)\
356 self.dir:draw()\
357\
358 elseif event.type == 'copy' then\
359 self:copy()\
360\
361 elseif event.type == 'checkbox_change' then\
362 if event.element == self.eject then\
363 config.eject = not not event.checked\
364 elseif event.element == self.automatic then\
365 config.automatic = not not event.checked\
366 end\
367\
368 Config.update('DiskCopy', config)\
369 event.element:draw()\
370\
371 else\
372 return UI.Page.eventHandler(self, event)\
373 end\
374 return true\
375end\
376\
377Event.on(\"disk\", function()\
378 page:scan()\
379 page:sync()\
380\
381 if config.automatic and not page.copyButton.inactive then\
382 page:copy()\
383 end\
384end)\
385\
386Event.on(\"disk_eject\", function()\
387 page:scan()\
388 page:sync()\
389end)\
390\
391Event.onTimeout(.2, function()\
392 page:scan()\
393 page:sync()\
394end)\
395\
396UI:setPage(page)\
397UI:start()",
398 [ "games/sameGame.lua" ] = "--Same Game for CraftOS 1.0.0 (ShinyCube) (Advanced Computer)\
399\
400-- slight modifications to run on a kiosk\
401local score, A, B, C, D, E\
402local board = {}\
403local selected = {}\
404local backup_board = {}\
405local backup_score\
406local backup_exists = false\
407local cnt_selected\
408local selected_color\
409local is_gameover\
410local best_scores = {}\
411local best_score_names = {}\
412local best_score_view = false\
413function init()\
414 loadScore()\
415 term.setBackgroundColor(colors.black)\
416 term.setTextColor(colors.white)\
417 term.clear()\
418 for i = 1, 10 do\
419 board[i] = {}\
420 selected[i] = {}\
421 backup_board[i] = {}\
422 end\
423 newGame()\
424 eventLoop()\
425end\
426function eventLoop()\
427 while true do\
428 local event, button, x, y = os.pullEvent()\
429 if event == \"mouse_click\" then\
430 if best_score_view then\
431 if y == 1 and 44 <= x and x <= 51 then\
432 best_score_view = false\
433 redraw()\
434 end\
435 else\
436 if x >= 7 and x <= 46 and y >=8 and y <= 17 then\
437 local j = math.floor((x-7)/2) + 1\
438 local i = (y-8) + 1\
439 clicked(i,j)\
440 end\
441 if y == 1 and 1 <= x and x <= 7 then\
442 newGame()\
443 end\
444 if y == 1 and 9 <= x and x <= 16 then\
445 undo()\
446 redraw()\
447 end\
448 if y == 1 and 18 <= x and x <= 31 then\
449 showBestScore()\
450 end\
451 if y == 1 and 33 <= x and x <= 42 then\
452 redraw()\
453 end\
454 if y == 1 and 44 <= x and x <= 51 then\
455 term.clear()\
456 term.setCursorPos(1,1)\
457 break\
458 end\
459 end\
460 end\
461 end\
462end\
463function newGame()\
464 score = 0\
465 A = 0\
466 B = 0\
467 C = 0\
468 D = 0\
469 E = 0\
470 cnt_selected = 0\
471 is_gameover = false\
472 backup_exists = false\
473 for i = 1, 10 do\
474 for j = 1, 20 do\
475 board[i][j] = math.random(5)\
476 if(board[i][j] == 1) then A = A + 1 end\
477 if(board[i][j] == 2) then B = B + 1 end\
478 if(board[i][j] == 3) then C = C + 1 end\
479 if(board[i][j] == 4) then D = D + 1 end\
480 if(board[i][j] == 5) then E = E + 1 end\
481 selected[i][j] = false\
482 end\
483 end\
484 redraw()\
485end\
486function redraw()\
487 if best_score_view then\
488 term.setCursorPos(1,1) term.write(\" [ BACK ]\")\
489 else\
490 term.setCursorPos(1,1) term.write(\"[ NEW ] [ UNDO ] [ HIGH SCORE ] [ SCREEN ] [ EXIT ]\")\
491 end\
492 term.setCursorPos(16,3) term.write(\"Same Game for Craft OS\")\
493 term.setCursorPos(15,5) term.write(\"Implemented by ShinyCube\")\
494 term.setCursorPos(3,19) term.write(\"Score: A: B: C: D: E: \")\
495 if best_score_view then\
496 for i = 1, 10 do\
497 term.setTextColor(colors.white)\
498 term.setBackgroundColor(colors.black)\
499 term.setCursorPos(7,8+(i-1))\
500 term.write(string.format(\"%2d. ...............................%5d\",i,best_scores[i]))\
501 term.setCursorPos(11,8+(i-1))\
502 term.write(best_score_names[i])\
503 end\
504 else\
505 for i = 1, 10 do\
506 for j = 1, 20 do\
507 term.setCursorPos(7+(j-1)*2,8+(i-1))\
508 if board[i][j] == 0 then\
509 term.blit(\". \",\"00\",\"ff\")\
510 elseif board[i][j] == 1 then\
511 if selected[i][j] then\
512 term.blit(\"A \",\"aa\",\"00\")\
513 else\
514 term.blit(\"A \",\"00\",\"aa\")\
515 end\
516 elseif board[i][j] == 2 then\
517 if selected[i][j] then\
518 term.blit(\"B \",\"bb\",\"00\")\
519 else\
520 term.blit(\"B \",\"00\",\"bb\")\
521 end\
522 elseif board[i][j] == 3 then\
523 if selected[i][j] then\
524 term.blit(\"C \",\"cc\",\"00\")\
525 else\
526 term.blit(\"C \",\"00\",\"cc\")\
527 end\
528 elseif board[i][j] == 4 then\
529 if selected[i][j] then\
530 term.blit(\"D \",\"dd\",\"00\")\
531 else\
532 term.blit(\"D \",\"00\",\"dd\")\
533 end\
534 elseif board[i][j] == 5 then\
535 if selected[i][j] then\
536 term.blit(\"E \",\"ee\",\"00\")\
537 else\
538 term.blit(\"E \",\"00\",\"ee\")\
539 end\
540 end\
541 end\
542 end\
543 end\
544 term.setTextColor(colors.white)\
545 term.setBackgroundColor(colors.black)\
546 term.setCursorPos(22,7)\
547 if is_gameover then\
548 term.write(\"GAME OVER\")\
549 else\
550 term.write(\" \")\
551 end\
552 term.setCursorPos(9,19)\
553 term.write(\" \")\
554 term.setCursorPos(9,19)\
555 term.write(score)\
556 if cnt_selected > 0 then\
557 term.write(\"+\" .. cnt_selected*cnt_selected-3*cnt_selected+4)\
558 end\
559 term.setCursorPos(23,19)\
560 term.write(A)\
561 term.setCursorPos(29,19)\
562 term.write(B)\
563 term.setCursorPos(35,19)\
564 term.write(C)\
565 term.setCursorPos(41,19)\
566 term.write(D)\
567 term.setCursorPos(47,19)\
568 term.write(E)\
569end\
570function deselectAll()\
571 for i = 1, 10 do\
572 for j = 1, 20 do\
573 selected[i][j] = false\
574 end\
575 end\
576 cnt_selected = 0\
577end\
578function rec_selection(i,j)\
579 if not selected[i][j] then\
580 selected[i][j] = true\
581 cnt_selected = cnt_selected + 1\
582 if i-1 >= 1 and board[i][j] == board[i-1][j] then rec_selection(i-1,j) end\
583 if i+1 <= 10 and board[i][j] == board[i+1][j] then rec_selection(i+1,j) end\
584 if j-1 >= 1 and board[i][j] == board[i][j-1] then rec_selection(i,j-1) end\
585 if j+1 <= 20 and board[i][j] == board[i][j+1] then rec_selection(i,j+1) end\
586 end\
587end\
588function backup()\
589 for i = 1, 10 do\
590 for j = 1, 20 do\
591 backup_board[i][j] = board[i][j]\
592 backup_score = score\
593 end\
594 end\
595 backup_exists = true\
596end\
597function removeSelected()\
598 local di, dj\
599 dj = 1\
600 for sj = 1, 20 do\
601 di = 10\
602 for si = 10, 1, -1 do\
603 if not selected[si][sj] then\
604 board[di][dj] = board[si][sj]\
605 di = di - 1\
606 end\
607 end\
608 for di = di, 1, -1 do\
609 board[di][dj] = 0\
610 end\
611 if board[10][dj] ~= 0 then dj = dj + 1 end\
612 end\
613 for dj = dj, 20 do\
614 for di = 1, 10 do\
615 board[di][dj] = 0\
616 end\
617 end\
618end\
619function checkGameOver()\
620 for i = 1, 10 do\
621 for j = 1, 20 do\
622 if i-1>=1 and board[i][j] > 0 and board[i][j] == board[i-1][j] then return false end\
623 if i+1<=10 and board[i][j] > 0 and board[i][j] == board[i+1][j] then return false end\
624 if j-1>=1 and board[i][j] > 0 and board[i][j] == board[i][j-1] then return false end\
625 if j+1<=20 and board[i][j] > 0 and board[i][j] == board[i][j+1] then return false end\
626 end\
627 end\
628 return true\
629end\
630function loadScore()\
631 local file = fs.open(\"same.dat\",\"r\")\
632 if file then\
633 for i = 1, 10 do\
634 best_score_names[i] = file.readLine() or \"NONAME\"\
635 best_scores[i] = tonumber(file.readLine()) or 0\
636 end\
637 file.close()\
638 else\
639 for i = 1, 10 do\
640 best_score_names[i] = \"NONAME\"\
641 best_scores[i] = 0\
642 end\
643 end\
644end\
645function saveScore()\
646 local file = fs.open(\"same.dat\",\"w\")\
647 if file then\
648 for i = 1, 10 do\
649 file.writeLine(best_score_names[i])\
650 file.writeLine(best_scores[i])\
651 end\
652 file.flush()\
653 end\
654end\
655function updateScore()\
656 local rank = 1\
657 for i = 10, 1, -1 do\
658 if best_scores[i] < score then\
659 best_score_names[i+1] = best_score_names[i]\
660 best_scores[i+1] = best_scores[i]\
661 else\
662 rank = i + 1\
663 break\
664 end\
665 end\
666 if rank <= 10 then\
667 best_score_names[rank] = getName(rank, score)\
668 best_scores[rank] = score\
669 saveScore()\
670 best_score_view = true\
671 redraw()\
672 end\
673end\
674function getName(rank, score)\
675 term.setTextColor(colors.white)\
676 term.setBackgroundColor(colors.black)\
677 term.clear()\
678 term.setCursorPos(1,1)\
679 print(\"Congratulation!\")\
680 print(\"You got a high score!\")\
681 print(\"Your score: \" .. score)\
682 print(\"Your rank: \" .. rank)\
683 print(\"Type your name. >\")\
684 local name = '...'\
685 name = string.sub(name, 1, 30)\
686 term.setTextColor(colors.white)\
687 term.setBackgroundColor(colors.black)\
688 term.clear()\
689 return name\
690end\
691function undo()\
692 if backup_exists then\
693 deselectAll()\
694 backup_exists = false\
695 score = backup_score\
696 for i = 1, 10 do\
697 for j = 1, 20 do\
698 board[i][j] = backup_board[i][j]\
699 end\
700 end\
701 end\
702end\
703function showBestScore()\
704 best_score_view = true\
705 redraw()\
706end\
707\
708function clicked(ci,cj)\
709 if selected[ci][cj] then\
710 backup()\
711 score = score + cnt_selected*cnt_selected-3*cnt_selected+4\
712 if selected_color == 1 then A = A - cnt_selected\
713 elseif selected_color == 2 then B = B - cnt_selected\
714 elseif selected_color == 3 then C = C - cnt_selected\
715 elseif selected_color == 4 then D = D - cnt_selected\
716 elseif selected_color == 5 then E = E - cnt_selected\
717 end\
718 removeSelected()\
719 deselectAll()\
720 if checkGameOver() then\
721 is_gameover = true\
722 backup_exists = false\
723 updateScore()\
724 redraw()\
725 else\
726 redraw()\
727 end\
728 else\
729 if cnt_selected > 0 then\
730 deselectAll()\
731 redraw()\
732 else\
733 if board[ci][cj] > 0 then\
734 selected_color = board[ci][cj]\
735 rec_selection(ci,cj)\
736 if cnt_selected == 1 then\
737 deselectAll()\
738 end\
739 redraw()\
740 end\
741 end\
742 end\
743end\
744init()",
745 [ "common/multiMiner.lua" ] = "local Event = require('opus.event')\
746local GPS = require('opus.gps')\
747local itemDB = require('core.itemDB')\
748local Point = require('opus.point')\
749local Socket = require('opus.socket')\
750local Sound = require('opus.sound')\
751local Util = require('opus.util')\
752local UI = require('opus.ui')\
753\
754local colors = _G.colors\
755local device = _G.device\
756local gps = _G.gps\
757local network = _G.network\
758local os = _G.os\
759\
760UI:configure('multiMiner', ...)\
761\
762local glasses = device['plethora:glasses']\
763local scanner = device['plethora:scanner'] or\
764 error('Plethora scanner must be equipped')\
765\
766-- hud\
767local canvas = glasses and glasses.canvas()\
768if canvas then\
769 local lh\
770\
771 local function addText(x, y, text, color)\
772 local th = canvas.group.addText({ x, y }, text, color or 0xa0a0a0FF)\
773 lh = lh or th.getLineHeight()\
774 th.setShadow(true)\
775 th.setScale(.75)\
776 return th\
777 end\
778\
779 canvas.group = canvas.addGroup({ 4, 90 })\
780 canvas.group.bg = canvas.group.addRectangle(0, 0, 80, 10, 0x40404080)\
781 canvas.group.addLines(\
782 { 0, 0 },\
783 { 80, 0 },\
784 { 80, 10 },\
785 { 0, 10 },\
786 { 0, 0 },\
787 0x202020FF,\
788 2)\
789 addText(20, 2, 'Swarm Miner', 0xc0c0c0FF)\
790\
791 local y = 15\
792 addText(3, y, 'Turtles')\
793 canvas.turtles = addText(60, y, '')\
794 canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)\
795\
796 y = y + lh + 5\
797 addText(3, y, 'Queue')\
798 canvas.queue = addText(60, y, '')\
799 canvas.group.addLine({ 0, y + lh - 2 }, { 80, y + lh - 2 }, 0x404040FF, 4)\
800end\
801\
802-- container\
803local canvas3d = glasses and glasses.canvas3d().create()\
804local box, offset\
805\
806local paused = false\
807\
808local function inBox(pt)\
809 if not box or not box.ex then\
810 return true\
811 end\
812 return Point.inBox(pt, box)\
813end\
814\
815local function locate()\
816 for _ = 1, 3 do\
817 local pt = GPS.getPoint()\
818 if pt then\
819 return pt\
820 end\
821 end\
822end\
823\
824local spt = GPS.getPoint() or error('GPS failure')\
825local chestPoint -- location of chest\
826local blockTypes = { } -- blocks types requested to mine\
827local turtles = { } -- active turtles\
828local pool = { } -- all turtles\
829local queue = { } -- actual blocks to mine\
830local abort\
831\
832local function hijackTurtle(remoteId)\
833 local socket, msg = Socket.connect(remoteId, 188)\
834\
835 if not socket then\
836 error(msg)\
837 end\
838\
839 socket:write('turtle')\
840 local methods = socket:read()\
841\
842 local hijack = { }\
843 for _,method in pairs(methods) do\
844 hijack[method] = function(...)\
845 socket:write({ method, ... })\
846 local resp = socket:read()\
847 if not resp then\
848 error('timed out: ' .. method)\
849 end\
850 return table.unpack(resp)\
851 end\
852 end\
853\
854 return hijack, socket\
855end\
856\
857local function getNextPoint(turtle)\
858 if not paused then\
859 local pt = Point.closest(turtle.getPoint(), queue)\
860 if pt then\
861 turtle.pt = pt\
862 queue[pt.pkey] = nil\
863 return pt\
864 end\
865 end\
866end\
867\
868local function run(member, point)\
869 Event.addRoutine(function()\
870 local turtle, socket\
871 local _, m = pcall(function()\
872 member.active = true\
873 turtle, socket = hijackTurtle(member.id)\
874\
875 local function emptySlots(retain, pt)\
876 local slots = turtle.getFilledSlots()\
877 for _,slot in pairs(slots) do\
878 if not retain[slot.key] and not slot.name:find('turtle') then\
879 turtle.select(slot.index)\
880 if pt then\
881 turtle.dropAt(pt, 64)\
882 else\
883 turtle.dropUp(64)\
884 end\
885 end\
886 end\
887 end\
888\
889 local function dropOff()\
890 -- go to 2 above chest\
891 local topPoint = Point.copy(chestPoint)\
892 topPoint.y = topPoint.y + 2\
893 turtle.gotoY(topPoint.y)\
894 while not turtle.go(topPoint) do\
895 os.sleep(.5)\
896 end\
897\
898 -- path to chest\
899 local box = Point.makeBox(\
900 { x = chestPoint.x - 3, y = chestPoint.y + 3, z = chestPoint.z - 3 },\
901 { x = chestPoint.x + 3, y = chestPoint.y, z = chestPoint.z + 3 }\
902 )\
903 turtle.set({\
904 movementStrategy = 'pathing',\
905 pathingBox = Point.normalizeBox(box),\
906 digPolicy = 'digNone',\
907 })\
908 while not turtle.moveAgainst(chestPoint) do\
909 os.sleep(.5)\
910 end\
911 emptySlots({ }, chestPoint)\
912\
913 -- path to 3 above chest\
914 turtle.pathfind(Point.above(topPoint))\
915 turtle.set({\
916 movementStrategy = 'goto',\
917 digPolicy = 'blacklist',\
918 })\
919 end\
920\
921 if turtle then\
922 turtles[member.id] = turtle\
923\
924 turtle.reset()\
925 turtle.set({\
926 attackPolicy = 'attack',\
927 digPolicy = 'blacklist',\
928 blacklist = {\
929 'turtle',\
930 'chest',\
931 'shulker',\
932 },\
933 movementStrategy = 'goto',\
934 point = point,\
935 })\
936 turtle.select(1)\
937\
938 repeat\
939 local pt = getNextPoint(turtle)\
940 if pt then\
941 member.status = 'digging'\
942\
943 if blockTypes[pt.key] == true then\
944 if turtle.moveAgainst(pt) then\
945 local index = turtle.selectOpenSlot()\
946 if turtle.digAt(pt, pt.name) then\
947 local slot = turtle.getSlot(index)\
948 if slot.count > 0 then\
949 blockTypes[pt.key] = slot.key\
950 if slot.key ~= pt.key then\
951 blockTypes[slot.key] = true\
952 end\
953 end\
954 end\
955 end\
956 turtle.select(1)\
957 else\
958 turtle.digAt(pt, pt.name)\
959 end\
960\
961 if turtle.getItemCount(15) > 0 then\
962 member.status = 'ejecting trash'\
963 emptySlots(blockTypes)\
964 turtle.condense()\
965 if turtle.getItemCount(15) > 0 then\
966 member.status = 'dropping off'\
967 if not chestPoint then\
968 member.abort = true\
969 member.status = 'full'\
970 else\
971 dropOff()\
972 end\
973 end\
974 turtle.select(1)\
975 end\
976 else\
977 member.status = 'waiting'\
978 os.sleep(1)\
979 end\
980 if member.fuel < 100 then\
981 member.status = 'out of fuel'\
982 break\
983 end\
984 until member.abort\
985 end\
986\
987 emptySlots(blockTypes)\
988\
989 if chestPoint then\
990 dropOff()\
991 while not turtle.go(Point.above(spt)) do\
992 os.sleep(.5)\
993 end\
994 --if turtle.selectSlotWithQuantity(0) then\
995 --turtle.set({ digPolicy = 'dig' })\
996 --end\
997 while not turtle.go(spt) do\
998 os.sleep(.5)\
999 end\
1000 else\
1001 turtle.gotoY(spt.y)\
1002 while not turtle.go(spt) do\
1003 os.sleep(.5)\
1004 end\
1005 end\
1006 end)\
1007\
1008 turtles[member.id] = nil\
1009 member.status = m\
1010 member.active = false\
1011 if socket then\
1012 socket:close()\
1013 end\
1014 end)\
1015end\
1016\
1017local function drawContainer(pos)\
1018 if canvas3d then\
1019 canvas3d.clear()\
1020\
1021 local function addBox(b)\
1022 canvas3d.addBox(\
1023 b.x - offset.x + .25,\
1024 b.y - offset.y + .25 ,\
1025 b.z - offset.z + .25 ,\
1026 .5, .5, .5).setDepthTested(false)\
1027 end\
1028 if box and box.ex then\
1029 addBox({ x = box.x, y = box.y, z = box.z })\
1030 addBox({ x = box.x, y = box.y, z = box.ez })\
1031 addBox({ x = box.ex, y = box.y, z = box.z })\
1032 addBox({ x = box.ex, y = box.y, z = box.ez })\
1033 addBox({ x = box.x, y = box.ey, z = box.z })\
1034 addBox({ x = box.x, y = box.ey, z = box.ez })\
1035 addBox({ x = box.ex, y = box.ey, z = box.z })\
1036 addBox({ x = box.ex, y = box.ey, z = box.ez })\
1037 elseif box then\
1038 canvas3d.recenter({ -(pos.x % 1), -(pos.y % 1), -(pos.z % 1) })\
1039 addBox(box)\
1040 end\
1041 end\
1042end\
1043\
1044local pauseResume = {\
1045 { text = 'Pause', event = 'pause' },\
1046 { text = 'Resume', event = 'resume' },\
1047}\
1048local containerText = {\
1049 [[Set a corner to contain mining area]],\
1050 [[Set ending corner]],\
1051 [[Set again to clear]],\
1052}\
1053\
1054local containTab = UI.Tab {\
1055 title = 'Contain',\
1056 button = UI.Button {\
1057 x = 2, y = 2,\
1058 text = 'Set corner',\
1059 event = 'contain'\
1060 },\
1061 textArea = UI.TextArea {\
1062 x = 2, y = 4,\
1063 value = containerText[1],\
1064 },\
1065}\
1066\
1067local blocksTab = UI.Tab {\
1068 title = 'Blocks',\
1069 grid = UI.ScrollingGrid {\
1070 y = 1,\
1071 columns = {\
1072 { heading = 'Count', key = 'count', width = 6, align = 'right' },\
1073 { heading = 'Name', key = 'displayName' },\
1074 },\
1075 sortColumn = 'displayName',\
1076 },\
1077}\
1078\
1079local turtlesTab = UI.Tab {\
1080 title = 'Turtles',\
1081 grid = UI.ScrollingGrid {\
1082 y = 1,\
1083 values = pool,\
1084 columns = {\
1085 { heading = 'ID', key = 'id', width = 5, },\
1086 { heading = ' Fuel', key = 'fuel', width = 5, align = 'right' },\
1087 { heading = ' Dist', key = 'distance', width = 5, align = 'right' },\
1088 { heading = 'Status', key = 'status' },\
1089 },\
1090 sortColumn = 'label',\
1091 },\
1092}\
1093\
1094local page = UI.Page {\
1095 menuBar = UI.MenuBar {\
1096 buttons = {\
1097 { text = 'Scan', event = 'scan' },\
1098 pauseResume[1],\
1099 { text = 'Abort', event = 'abort', x = -7 },\
1100 },\
1101 },\
1102 tabs = UI.Tabs {\
1103 y = 2, ey = -2,\
1104 [1] = blocksTab,\
1105 [2] = turtlesTab,\
1106 [3] = containTab,\
1107 },\
1108 info = UI.Window {\
1109 y = -1,\
1110 backgroundColor = colors.blue,\
1111 }\
1112}\
1113\
1114function page.info:draw()\
1115 self:clear()\
1116 self:write(2, 1, 'Turtles: ' .. Util.size(turtles))\
1117 if not chestPoint then\
1118 self:write(16, 1, 'No chest')\
1119 end\
1120 self:write(28, 1, 'Queue: ' .. Util.size(queue))\
1121end\
1122\
1123function turtlesTab.grid:getDisplayValues(row)\
1124 row = Util.shallowCopy(row)\
1125 row.distance = row.distance and Util.round(row.distance, 1)\
1126 row.fuel = row.fuel and row.fuel > 0 and Util.toBytes(row.fuel) or ''\
1127 return row\
1128end\
1129\
1130function page:scan()\
1131 local gpt = GPS.getPoint()\
1132 if not gpt then\
1133 return\
1134 end\
1135 local rawBlocks = scanner:scan()\
1136 local candidates = { }\
1137\
1138 self.totals = Util.reduce(rawBlocks,\
1139 function(acc, b)\
1140 b.key = table.concat({ b.name, b.metadata }, ':')\
1141 local entry = acc[b.key]\
1142 if not entry then\
1143 b.displayName = itemDB:getName(b.key)\
1144 b.count = 1\
1145 acc[b.key] = b\
1146 else\
1147 entry.count = entry.count + 1\
1148 end\
1149\
1150 if b.name == 'computercraft:turtle_advanced' or\
1151 b.name == 'computercraft:turtle_expanded' or\
1152 b.name == 'computercraft:turtle' then\
1153 table.insert(candidates, b)\
1154 end\
1155\
1156 if b.name == 'minecraft:chest' or b.name:find('shulker') then\
1157 chestPoint = b\
1158 end\
1159\
1160 -- add relevant blocks to queue\
1161 b.x = gpt.x + b.x\
1162 b.y = gpt.y + b.y\
1163 b.z = gpt.z + b.z\
1164 b.pkey = table.concat({ b.x, b.y, b.z }, ':')\
1165 if blockTypes[b.key] and inBox(b) then\
1166 if not Util.any(turtles, function(t)\
1167 return t.pt and t.pt.pkey == b.pkey\
1168 end) then\
1169 queue[b.pkey] = b\
1170 end\
1171 else\
1172 queue[b.pkey] = nil\
1173 end\
1174 return acc\
1175 end,\
1176 { })\
1177\
1178 for _, b in pairs(candidates) do\
1179 local v = scanner.getBlockMeta(b.x - gpt.x, b.y - gpt.y, b.z - gpt.z)\
1180 if v and v.computer then\
1181 local member = pool[v.computer.id]\
1182 if not member then\
1183 member = {\
1184 id = v.computer.id,\
1185 label = v.computer.label,\
1186 }\
1187 pool[v.computer.id] = member\
1188 end\
1189\
1190 member.fuel = v.turtle.fuel\
1191 member.distance = 0\
1192\
1193 if not v.computer.isOn then\
1194 member.status = 'Powered off'\
1195 elseif v.turtle.fuel < 100 and not member.active then\
1196 member.status = 'Not enough fuel'\
1197 elseif not member.active and not member.abort then\
1198 local pt = Point.copy(b)\
1199 pt.heading = Point.facings[v.state.facing].heading\
1200 run(member, pt)\
1201 end\
1202 end\
1203 end\
1204end\
1205\
1206function blocksTab.grid:getDisplayValues(row)\
1207 row = Util.shallowCopy(row)\
1208 row.count = Util.toBytes(row.count) .. ' '\
1209 return row\
1210end\
1211\
1212function blocksTab.grid:getRowTextColor(row, selected)\
1213 return blockTypes[row.key] and\
1214 colors.yellow or\
1215 UI.Grid.getRowTextColor(self, row, selected)\
1216end\
1217\
1218function blocksTab:eventHandler(event)\
1219 if event.type == 'grid_select' then\
1220 local key = event.selected.key\
1221 if blockTypes[key] then\
1222 for k,v in pairs(queue) do\
1223 if v.key == key then\
1224 queue[k] = nil\
1225 end\
1226 end\
1227 blockTypes[key] = nil\
1228 else\
1229 blockTypes[key] = true\
1230 end\
1231 self.grid:draw()\
1232 end\
1233end\
1234\
1235function page:eventHandler(event)\
1236 if event.type == 'scan' then\
1237 blocksTab.grid:setValues(self.totals)\
1238 blocksTab.grid:draw()\
1239 self.tabs:selectTab(blocksTab)\
1240\
1241 elseif event.type == 'pause' then\
1242 paused = true\
1243 Util.merge(event.button, pauseResume[2])\
1244 event.button:draw()\
1245\
1246 elseif event.type == 'resume' then\
1247 paused = false\
1248 Util.merge(event.button, pauseResume[1])\
1249 event.button:draw()\
1250\
1251 elseif event.type == 'contain' then\
1252 local pt = { gps.locate() }\
1253 local pos = {\
1254 x = pt[1],\
1255 y = pt[2],\
1256 z = pt[3],\
1257 }\
1258\
1259 if not box then\
1260 offset = {\
1261 x = math.floor(pos.x),\
1262 y = math.floor(pos.y),\
1263 z = math.floor(pos.z),\
1264 }\
1265 box = {\
1266 x = math.floor(pos.x),\
1267 y = math.floor(pos.y) - 1,\
1268 z = math.floor(pos.z),\
1269 }\
1270 containTab.textArea.value = containerText[2]\
1271 elseif not box.ex then\
1272 box.ex = math.floor(pos.x)\
1273 box.ey = math.floor(pos.y) - 1\
1274 box.ez = math.floor(pos.z)\
1275 box = Point.normalizeBox(box)\
1276 containTab.textArea.value = containerText[3]\
1277 else\
1278 box = nil\
1279 containTab.textArea.value = containerText[1]\
1280 end\
1281\
1282 containTab.textArea:draw()\
1283 drawContainer(pos)\
1284\
1285 elseif event.type == 'abort' then\
1286 for _, v in pairs(pool) do\
1287 v.abort = true\
1288 v.status = 'aborting'\
1289 end\
1290 spt = Point.above(locate())\
1291 abort = true\
1292 end\
1293\
1294 UI.Page.eventHandler(self, event)\
1295end\
1296\
1297Event.onInterval(5, function()\
1298 if not abort and not paused then\
1299\
1300 --local meta = scanner.getMetaOwner()\
1301 --if meta.isSneaking then\
1302 page:scan()\
1303 -- Sound.play('entity.bobber.throw', .6)\
1304 --end\
1305 end\
1306end)\
1307\
1308Event.onInterval(1, function()\
1309 for id,v in pairs(network) do\
1310 if v.fuel then\
1311 if pool[id] then\
1312 pool[id].fuel = v.fuel\
1313 pool[id].distance = v.distance\
1314 end\
1315 end\
1316 end\
1317\
1318 if abort and Util.size(turtles) == 0 then\
1319 Event.exitPullEvents()\
1320 end\
1321\
1322 if turtlesTab.enabled then\
1323 turtlesTab.grid:update()\
1324 turtlesTab.grid:draw()\
1325 end\
1326\
1327 page.info:draw()\
1328 page.info:sync()\
1329\
1330 if canvas then\
1331 canvas.turtles.setText(tostring(Util.size(turtles)))\
1332 canvas.queue.setText(tostring(Util.size(queue)))\
1333 end\
1334end)\
1335\
1336Event.onTimeout(.1, function()\
1337 page:scan()\
1338 blocksTab.grid:setValues(page.totals)\
1339 blocksTab.grid:draw()\
1340 page:sync()\
1341end)\
1342\
1343UI:setPage(page)\
1344\
1345--[[\
1346Event.onTerminate(function()\
1347 spt = Point.above(locate())\
1348 for _, v in pairs(pool) do\
1349 v.status = 'aborting'\
1350 v.abort = true\
1351 end\
1352 abort = true\
1353end)\
1354]]\
1355\
1356Event.pullEvents()\
1357\
1358if canvas then\
1359 canvas3d.clear()\
1360 canvas.group.remove()\
1361end",
1362 [ "core/.package" ] = "{\
1363 title = 'APIs used by various programs',\
1364 repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/core',\
1365 description = [[Provides APIs used by Opus applications.]],\
1366 license = 'MIT',\
1367}",
1368 [ "monitor/termShare.lua" ] = "local Util = require('opus.util')\
1369\
1370local device = _G.device\
1371local multishell = _ENV.multishell\
1372local os = _G.os\
1373local term = _G.term\
1374\
1375-- list this terminal in the devices list so it's available via\
1376-- peripheral sharing\
1377\
1378local args = Util.parse(...)\
1379local name = args[1] or error('Syntax: termShare [--title=title] term_name')\
1380local title = args.title\
1381\
1382device[name] = term.current()\
1383device[name].name = name\
1384device[name].side = name\
1385device[name].type = 'terminal'\
1386\
1387if title then\
1388 multishell.setTitle(multishell.getCurrent(), title)\
1389end\
1390os.pullEventRaw('terminate')\
1391os.queueEvent('peripheral_detach', name)",
1392 [ "common/.package" ] = "{\
1393 title = 'Useful applications',\
1394 repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/common',\
1395 description = [[Various applications for Opus.\
1396\
1397* App Store\
1398* Peripheral API viewer\
1399* Disk / Computer copier\
1400* A better editor (copy/paste/undo)\
1401* Turtle swarm miner\
1402* Turtle follow\
1403* Screen recorder\
1404* and more...\
1405 ]],\
1406 license = 'MIT',\
1407 required = {\
1408 'core',\
1409 },\
1410}",
1411 [ "common/SoundPlayer.lua" ] = "local Sound = require('opus.sound')\
1412local UI = require('opus.ui')\
1413local Util = require('opus.util')\
1414\
1415local peripheral = _G.peripheral\
1416\
1417if not peripheral.find('speaker') then\
1418 error('No speaker attached')\
1419end\
1420\
1421local rawSounds = Util.readLines('packages/common/etc/sounds.txt') or error('Unable to read sounds file')\
1422local sounds = { }\
1423for _, s in pairs(rawSounds) do\
1424 table.insert(sounds, { name = s })\
1425end\
1426\
1427UI:configure('SoundPlayer', ...)\
1428\
1429local page = UI.Page {\
1430 labelText = UI.Text {\
1431 x = 3, y = 2,\
1432 value = 'Search',\
1433 },\
1434 filter = UI.TextEntry {\
1435 x = 10, y = 2, ex = -3,\
1436 limit = 32,\
1437 },\
1438 grid = UI.ScrollingGrid {\
1439 y = 4,\
1440 columns = {\
1441 { heading = 'Name', key = 'name' },\
1442 },\
1443 values = sounds,\
1444 },\
1445}\
1446\
1447function page:eventHandler(event)\
1448 if event.type == 'grid_select' then\
1449 Sound.play(event.selected.name)\
1450\
1451 elseif event.type == 'text_change' then\
1452 if not event.text then\
1453 self.grid.values = sounds\
1454 else\
1455 self.grid.values = { }\
1456 for _,f in pairs(sounds) do\
1457 if string.find(f.name, event.text) then\
1458 table.insert(self.grid.values, f)\
1459 end\
1460 end\
1461 end\
1462 self.grid:update()\
1463 self.grid:setIndex(1)\
1464 self.grid:draw()\
1465\
1466 else\
1467 return UI.Page.eventHandler(self, event)\
1468 end\
1469 return true\
1470end\
1471\
1472UI:setPage(page)\
1473UI:start()",
1474 [ "common/etc/scripts/abort" ] = "turtle.abort(true)",
1475 [ "common/recorder.lua" ] = "-- +---------------------+------------+---------------------+\
1476-- | | | |\
1477-- | | RecGif | |\
1478-- | | | |\
1479-- +---------------------+------------+---------------------+\
1480\
1481local version = \"Version 1.1.6\"\
1482\
1483-- Records your terminal and saves the result as an animating GIF.\
1484-- http://www.computercraft.info/forums2/index.php?/topic/24840-recgif/\
1485\
1486-- ----------------------------------------------------------\
1487\
1488-- Original code by Bomb Bloke\
1489-- Modified to integrate with opus os\
1490\
1491local Util = require('opus.util')\
1492\
1493local multishell = _ENV.multishell\
1494local os = _G.os\
1495\
1496local colours = _G.colors\
1497\
1498local args, options = Util.parse(...)\
1499\
1500local oldTerm, showInput, skipLast, lastDelay, curInput = Util.shallowCopy(_G.device.terminal), false, false, 2, \"\"\
1501local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()\
1502local greys, buttons = {[\"0\"] = true, [\"7\"] = true, [\"8\"] = true, [\"f\"] = true}, {\"l\", \"r\", \"m\"}\
1503local charW, charH, chars\
1504\
1505local calls = { }\
1506local curCalls = { delay = 0 }\
1507local callListCount = 0\
1508local callCount = 0\
1509\
1510local function showSyntax()\
1511 print('Gif Recorder by Bomb Bloke\\n')\
1512 print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')\
1513 print(' --showInput : show input')\
1514 print(' --skipLast : skip last')\
1515 print(' --lastDelay : last delay')\
1516 print(' --noResize : dont resize')\
1517end\
1518\
1519if options.showInput then\
1520 showInput, ySize = true, ySize + 1\
1521end\
1522\
1523if options.skipLast then\
1524 skipLast = true\
1525end\
1526\
1527if options.lastDelay then\
1528 lastDelay = options.lastDelay\
1529end\
1530\
1531if options.help then\
1532 showSyntax()\
1533 return\
1534end\
1535\
1536if options.daemon then\
1537 _G.device.keyboard.addHotkey('control-P', function()\
1538 multishell.openTab(_ENV, {\
1539 path = 'sys/apps/shell.lua',\
1540 args = { arg[0], '--noResize', '--rawOutput', 'recorder.gif' },\
1541 })\
1542 end)\
1543 return\
1544end\
1545\
1546print('Gif Recorder by Bomb Bloke')\
1547print(version)\
1548print('\\nPress control-p to stop recording')\
1549\
1550local filename = args[1]\
1551if not filename then\
1552 print('Enter file name:')\
1553 filename = read()\
1554end\
1555\
1556if #filename == 0 then\
1557 showSyntax()\
1558 print()\
1559 error('Invalid file name')\
1560end\
1561\
1562print('Initializing...')\
1563\
1564-- don't pollute global env\
1565-- convert these to require style apis\
1566local function loadAPI(url, env)\
1567 local apiEnv = Util.shallowCopy(env)\
1568 apiEnv.shell = nil\
1569 apiEnv.multishell = nil\
1570 setmetatable(apiEnv, { __index = _G })\
1571 local fn, m = Util.loadUrl(url, apiEnv)\
1572 if not fn then\
1573 error(m)\
1574 end\
1575 fn()\
1576 return apiEnv\
1577end\
1578\
1579bbpack = loadAPI('http://pastebin.com/raw/cUYTGbpb', getfenv(1))\
1580GIF = loadAPI('http://pastebin.com/raw/5uk9uRjC', getfenv(1))\
1581\
1582local s, m = Util.runUrl(getfenv(1), 'http://pastebin.com/raw/cUYTGbpb', 'get', 'CnLzL5fg')\
1583if not s then\
1584 error(m)\
1585end\
1586-- 'Y0eLUPtr')\
1587-- CnLzL5fg\
1588local function snooze()\
1589 local myEvent = tostring({})\
1590 os.queueEvent(myEvent)\
1591 os.pullEvent(myEvent)\
1592end\
1593\
1594local function safeString(text)\
1595 local newText = {}\
1596\
1597 for i = 1, #text do\
1598 local val = text:byte(i)\
1599 newText[i] = (val > 31 and val < 127) and val or 63\
1600 end\
1601\
1602 return string.char(unpack(newText))\
1603end\
1604\
1605local function safeCol(text, subst)\
1606 local newText = {}\
1607\
1608 for i = 1, #text do\
1609 local val = text:sub(i, i)\
1610 newText[i] = greys[val] and val or subst\
1611 end\
1612\
1613 return table.concat(newText)\
1614end\
1615\
1616-- Build a terminal that records stuff:\
1617\
1618local recTerm = _G.device.terminal\
1619\
1620for key, func in pairs(oldTerm) do\
1621 if type(func) == 'function' then\
1622 recTerm[key] = function(...)\
1623 local result = { func(...) }\
1624\
1625 if callCount == 0 then\
1626 os.queueEvent('capture_frame')\
1627 end\
1628 callCount = callCount + 1\
1629 curCalls[callCount] = { key, ... }\
1630 return table.unpack(result)\
1631 end\
1632 end\
1633end\
1634\
1635local tabId = multishell.getCurrent()\
1636multishell.hideTab(tabId)\
1637\
1638if not options.noResize then\
1639 os.queueEvent('term_resize')\
1640end\
1641\
1642_G.device.keyboard.addHotkey('control-p', function()\
1643 os.queueEvent('recorder_stop')\
1644end)\
1645\
1646local curTime = os.clock() - 1\
1647\
1648while true do\
1649 local event = { os.pullEventRaw() }\
1650\
1651 if event[1] == 'recorder_stop' or event[1] == 'terminate' then\
1652 break\
1653 end\
1654\
1655 if event[1] == 'capture_frame' then\
1656 local newTime = os.clock()\
1657\
1658 if callListCount > 0 then\
1659 calls[callListCount].delay = (newTime - curTime)\
1660 end\
1661\
1662 curTime = newTime\
1663 callListCount = callListCount + 1\
1664 calls[callListCount] = curCalls\
1665\
1666 curCalls, callCount = { delay = 0 }, 0\
1667 end\
1668end\
1669\
1670_G.device.keyboard.removeHotkey('control-p')\
1671\
1672for k,fn in pairs(oldTerm) do\
1673 _G.device.terminal[k] = fn\
1674end\
1675\
1676multishell.unhideTab(tabId)\
1677multishell.setFocus(tabId)\
1678\
1679if #calls[#calls] == 0 then calls[#calls] = nil end\
1680if skipLast and #calls > 1 then calls[#calls] = nil end\
1681\
1682calls[#calls].delay = lastDelay\
1683\
1684if options.rawOutput then\
1685 Util.writeTable('tmp/raw.txt', calls)\
1686 return\
1687end\
1688\
1689print(string.format(\"Encoding %d frames...\", #calls))\
1690\
1691-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):\
1692\
1693do\
1694 local callListCount, tempCalls, blink, oldBlink, curBlink, blinkDelay = 1, {}, false, false, true, 0\
1695\
1696 for i = 1, #calls - 1 do\
1697 curCalls = calls[i]\
1698 tempCalls[callListCount] = curCalls\
1699 for j = 1, #curCalls do if curCalls[j][1] == \"setCursorBlink\" then blink = curCalls[j][2] end end\
1700\
1701 if blink then\
1702 if blinkDelay == 0 then\
1703 curCalls[#curCalls + 1] = {\"toggleCur\", curBlink}\
1704 blinkDelay, curBlink = 0.4, not curBlink\
1705 end\
1706\
1707 while tempCalls[callListCount].delay > blinkDelay do\
1708 local remainder = tempCalls[callListCount].delay - blinkDelay\
1709 tempCalls[callListCount].delay = blinkDelay\
1710 callListCount = callListCount + 1\
1711 tempCalls[callListCount] = {{\"toggleCur\", curBlink}, [\"delay\"] = remainder}\
1712 blinkDelay, curBlink = 0.4, not curBlink\
1713 end\
1714\
1715 blinkDelay = blinkDelay - tempCalls[callListCount].delay\
1716 else\
1717 if oldBlink then curCalls[#curCalls + 1] = {\"toggleCur\", false} end\
1718 blinkDelay = (curCalls.delay - blinkDelay) % 0.4\
1719 end\
1720\
1721 callListCount, oldBlink = callListCount + 1, blink\
1722 end\
1723\
1724 tempCalls[callListCount] = calls[#calls]\
1725 tempCalls[callListCount][#tempCalls[callListCount] + 1] = {\"toggleCur\", false}\
1726\
1727 calls, curCalls = tempCalls, nil\
1728end\
1729\
1730snooze()\
1731\
1732-- Load font data:\
1733do\
1734 local ascii, counter = GIF.toPaintutils(GIF.flattenGIF(GIF.loadGIF(\"ascii.gif\"))), 0\
1735 local newFont, ybump, xbump = #ascii ~= #ascii[1], 0, 0\
1736 charW, charH, chars = newFont and #ascii[1] / 16 or #ascii[1] * 3 / 64, #ascii / 16, {}\
1737\
1738 for yy = 0, newFont and 15 or 7 do\
1739 for xx = 0, 15 do\
1740 local newChar, length = {}, 0\
1741\
1742 -- Place in 2d grid of bools:\
1743 for y = 1, charH do\
1744 local newRow = {}\
1745\
1746 for x = 1, charW do\
1747 local set = ascii[y + ybump][x + xbump] == 1\
1748 if set and x > length then length = x end\
1749 newRow[x] = set\
1750 end\
1751\
1752 newChar[y] = newRow\
1753 end\
1754\
1755 -- Center:\
1756 if not newFont then for y = 1, charH do for x = 1, math.floor((charW - length) / 2) do table.insert(newChar[y], 1, false) end end end\
1757\
1758 chars[counter] = newChar\
1759 counter, xbump = counter + 1, xbump + (newFont and charW or charH)\
1760 end\
1761 xbump, ybump = 0, ybump + charH\
1762 end\
1763end\
1764\
1765snooze()\
1766\
1767-- Terminal data translation:\
1768\
1769do\
1770 local hex, counter = \"0123456789abcdef\", 1\
1771\
1772 for i = 1, 16 do\
1773 colourNum[counter] = hex:sub(i, i)\
1774 counter = counter * 2\
1775 end\
1776end\
1777\
1778for y = 1, ySize do\
1779 buffer[y] = {}\
1780 for x = 1, xSize do buffer[y][x] = {\" \", colourNum[tCol], colourNum[bCol]} end\
1781end\
1782\
1783if showInput then for x = 1, xSize do buffer[ySize][x][3] = colourNum[colours.lightGrey] end end\
1784\
1785tTerm.blit = function(text, fgCol, bgCol)\
1786 if xPos > xSize or xPos + #text - 1 < 1 or yPos < 1 or yPos > ySize then return end\
1787\
1788 if not _HOST then text = safeString(text) end\
1789\
1790 if not term.isColour() then\
1791 fgCol = safeCol(fgCol, \"0\")\
1792 bgCol = safeCol(bgCol, \"f\")\
1793 end\
1794\
1795 if xPos < 1 then\
1796 text = text:sub(2 - xPos)\
1797 fgCol = fgCol:sub(2 - xPos)\
1798 bgCol = bgCol:sub(2 - xPos)\
1799 xPos = 1\
1800 end\
1801\
1802 if xPos + #text - 1 > xSize then\
1803 text = text:sub(1, xSize - xPos + 1)\
1804 fgCol = fgCol:sub(1, xSize - xPos + 1)\
1805 bgCol = bgCol:sub(1, xSize - xPos + 1)\
1806 end\
1807\
1808 for x = 1, #text do\
1809 buffer[yPos][xPos + x - 1][1] = text:sub(x, x)\
1810 buffer[yPos][xPos + x - 1][2] = fgCol:sub(x, x)\
1811 buffer[yPos][xPos + x - 1][3] = bgCol:sub(x, x)\
1812 end\
1813\
1814 xPos = xPos + #text\
1815end\
1816\
1817tTerm.write = function(text)\
1818 text = tostring(text)\
1819 tTerm.blit(text, string.rep(colourNum[tCol], #text), string.rep(colourNum[bCol], #text))\
1820end\
1821\
1822tTerm.clearLine = function()\
1823 local oldXPos = xPos\
1824\
1825 xPos = 1\
1826 tTerm.write(string.rep(\" \", xSize))\
1827\
1828 xPos = oldXPos\
1829end\
1830\
1831tTerm.clear = function()\
1832 local oldXPos, oldYPos = xPos, yPos\
1833\
1834 for y = 1, ySize do\
1835 xPos, yPos = 1, y\
1836 tTerm.write(string.rep(\" \", xSize))\
1837 end\
1838\
1839 xPos, yPos = oldXPos, oldYPos\
1840end\
1841\
1842tTerm.setCursorPos = function(x, y)\
1843 xPos, yPos = math.floor(x), math.floor(y)\
1844end\
1845\
1846tTerm.setTextColour = function(col)\
1847 tCol = col\
1848end\
1849\
1850tTerm.setTextColor = function(col)\
1851 tCol = col\
1852end\
1853\
1854tTerm.setBackgroundColour = function(col)\
1855 bCol = col\
1856end\
1857\
1858tTerm.setBackgroundColor = function(col)\
1859 bCol = col\
1860end\
1861\
1862tTerm.scroll = function(lines)\
1863 if math.abs(lines) < ySize then\
1864 local oldXPos, oldYPos = xPos, yPos\
1865\
1866 for y = 1, ySize do\
1867 if y + lines > 0 and y + lines <= ySize then\
1868 for x = 1, xSize do\
1869 xPos, yPos = x, y\
1870 tTerm.blit(buffer[y + lines][x][1], buffer[y + lines][x][2], buffer[y + lines][x][3])\
1871 end\
1872 else\
1873 yPos = y\
1874 tTerm.clearLine()\
1875 end\
1876 end\
1877\
1878 xPos, yPos = oldXPos, oldYPos\
1879 else tTerm.clear() end\
1880end\
1881\
1882tTerm.toggleCur = function(newBlink)\
1883 curBlink = newBlink\
1884end\
1885\
1886tTerm.newInput = function(input)\
1887 local oldTC, oldBC, oldX, oldY = tCol, bCol, xPos, yPos\
1888 tCol, bCol, xPos, yPos, ySize, input = colours.grey, colours.lightGrey, 1, ySize + 1, ySize + 1, input .. \" \"\
1889\
1890 while #curInput + #input + 1 > xSize do curInput = curInput:sub(curInput:find(\" \") + 1) end\
1891 curInput = curInput .. input .. \" \"\
1892 tTerm.clearLine()\
1893 tTerm.write(curInput)\
1894\
1895 tCol, bCol, xPos, yPos, ySize = oldTC, oldBC, oldX, oldY, ySize - 1\
1896end\
1897\
1898tTerm.key = function(key)\
1899 tTerm.newInput((not keys.getName(key)) and \"unknownKey\" or keys.getName(key))\
1900end\
1901\
1902tTerm.mouse_click = function(button, x, y)\
1903 tTerm.newInput(buttons[button] .. \"C@\" .. tostring(x) .. \"x\" .. tostring(y))\
1904end\
1905\
1906local image = {[\"width\"] = xSize * charW, [\"height\"] = ySize * charH}\
1907\
1908for i = 1, #calls do\
1909 local xMin, yMin, xMax, yMax, oldBuffer, curCalls, changed = xSize + 1, ySize + 1, 0, 0, {}, calls[i], false\
1910 calls[i] = nil\
1911\
1912 for y = 1, ySize do\
1913 oldBuffer[y] = {}\
1914 for x = 1, xSize do oldBuffer[y][x] = {buffer[y][x][1], buffer[y][x][2], buffer[y][x][3], buffer[y][x][4]} end\
1915 end\
1916\
1917 snooze()\
1918\
1919 if showInput then ySize = ySize - 1 end\
1920 for j = 1, #curCalls do if tTerm[curCalls[j][1]] then tTerm[curCalls[j][1]](unpack(curCalls[j], 2)) end end\
1921 if showInput then ySize = ySize + 1 end\
1922\
1923 if i > 1 then\
1924 for yy = 1, ySize do for xx = 1, xSize do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= \" \") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] then\
1925 changed = true\
1926 if xx < xMin then xMin = xx end\
1927 if xx > xMax then xMax = xx end\
1928 if yy < yMin then yMin = yy end\
1929 if yy > yMax then yMax = yy end\
1930 end end end\
1931 else xMin, yMin, xMax, yMax, changed = 1, 1, xSize, ySize, true end\
1932\
1933 if oldBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not curBlink) and oldXPos > 0 and oldYPos > 0 and oldXPos <= xSize and oldYPos <= ySize then\
1934 changed = true\
1935 if oldXPos < xMin then xMin = oldXPos end\
1936 if oldXPos > xMax then xMax = oldXPos end\
1937 if oldYPos < yMin then yMin = oldYPos end\
1938 if oldYPos > yMax then yMax = oldYPos end\
1939 buffer[oldYPos][oldXPos][4] = false\
1940 end\
1941\
1942 if curBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not oldBlink) and xPos > 0 and yPos > 0 and xPos <= xSize and yPos <= ySize then\
1943 changed = true\
1944 if xPos < xMin then xMin = xPos end\
1945 if xPos > xMax then xMax = xPos end\
1946 if yPos < yMin then yMin = yPos end\
1947 if yPos > yMax then yMax = yPos end\
1948 buffer[yPos][xPos][4] = true\
1949 end\
1950\
1951 oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos\
1952\
1953 local thisFrame = {\
1954 [\"xstart\"] = (xMin - 1) * charW,\
1955 [\"ystart\"] = (yMin - 1) * charH,\
1956 [\"xend\"] = (xMax - xMin + 1) * charW,\
1957 [\"yend\"] = (yMax - yMin + 1) * charH,\
1958 [\"delay\"] = curCalls.delay,\
1959 [\"disposal\"] = 1\
1960 }\
1961\
1962 for y = 1, (yMax - yMin + 1) * charH do\
1963 local row = {}\
1964 for x = 1, (xMax - xMin + 1) * charW do row[x] = \" \" end\
1965 thisFrame[y] = row\
1966 end\
1967\
1968 snooze()\
1969\
1970 for yy = yMin, yMax do\
1971 local yBump = (yy - yMin) * charH\
1972\
1973 for xx = xMin, xMax do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= \" \") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] or buffer[yy][xx][4] ~= oldBuffer[yy][xx][4] or i == 1 then\
1974 local thisChar, thisT, thisB, xBump = chars[buffer[yy][xx][1]:byte()], buffer[yy][xx][2], buffer[yy][xx][3], (xx - xMin) * charW\
1975if thisChar then\
1976 for y = 1, charH do\
1977 for x = 1, charW do\
1978 local ch = thisChar[y][x] and thisT or thisB\
1979 thisFrame[y + yBump][x + xBump] = ch\
1980 end\
1981 end\
1982end\
1983\
1984 if buffer[yy][xx][4] then\
1985 thisT, thisChar = colourNum[tCol], chars[95]\
1986 for y = 1, charH do for x = 1, charW do if thisChar[y][x] then thisFrame[y + yBump][x + xBump] = thisT end end end\
1987 end\
1988 end end\
1989\
1990 for y = yBump + 1, yBump + charH do\
1991 local skip, chars, row = 0, {}, {}\
1992\
1993 for x = 1, #thisFrame[y] do\
1994 if thisFrame[y][x] == \" \" then\
1995 if #chars > 0 then\
1996 row[#row + 1] = table.concat(chars)\
1997 chars = {}\
1998 end\
1999\
2000 skip = skip + 1\
2001 else\
2002 if skip > 0 then\
2003 row[#row + 1] = skip\
2004 skip = 0\
2005 end\
2006\
2007 chars[#chars + 1] = thisFrame[y][x]\
2008 end\
2009 end\
2010\
2011 if #chars > 0 then row[#row + 1] = table.concat(chars) end\
2012 thisFrame[y] = row\
2013 end\
2014\
2015 snooze()\
2016 end\
2017\
2018 if changed then \
2019 image[#image + 1] = thisFrame\
2020 else\
2021 image[#image].delay = image[#image].delay + curCalls.delay\
2022 end\
2023end\
2024\
2025buffer = nil\
2026\
2027GIF.saveGIF(image, filename)\
2028\
2029fs.delete('ascii.gif')\
2030\
2031print(\"Encode complete\")",
2032 [ "core/apis/refinedAdapter.lua" ] = "local class = require('opus.class')\
2033local itemDB = require('core.itemDB')\
2034local Peripheral = require('opus.peripheral')\
2035local Util = require('opus.util')\
2036\
2037local RefinedAdapter = class()\
2038\
2039function RefinedAdapter:init(args)\
2040 local defaults = {\
2041 name = 'refinedStorage',\
2042 }\
2043 Util.merge(self, defaults)\
2044 Util.merge(self, args)\
2045\
2046 local controller\
2047 if not self.side then\
2048 controller = Peripheral.getByMethod('getCraftingTasks')\
2049 else\
2050 controller = Peripheral.getBySide(self.side)\
2051 end\
2052\
2053 if controller then\
2054 Util.merge(self, controller)\
2055 end\
2056end\
2057\
2058function RefinedAdapter:isValid()\
2059 return not not self.getCraftingTasks\
2060end\
2061\
2062function RefinedAdapter:getItemDetails(item)\
2063 local detail = self.findItems(item)\
2064 if detail and #detail > 0 then\
2065 return detail[1].getMetadata()\
2066 end\
2067end\
2068\
2069function RefinedAdapter:getCachedItemDetails(item)\
2070 local cached = itemDB:get(item)\
2071 if cached then\
2072 return cached\
2073 end\
2074\
2075 local detail = self:getItemDetails(item)\
2076 if detail then\
2077 return itemDB:add(detail)\
2078 end\
2079end\
2080\
2081function RefinedAdapter:refresh(throttle)\
2082 return self:listItems(throttle)\
2083end\
2084\
2085function RefinedAdapter:listItems(throttle)\
2086 local items = { }\
2087 throttle = throttle or Util.throttle()\
2088\
2089 local s, m = pcall(function()\
2090 for _,v in pairs(self.listAvailableItems()) do\
2091 --if v.count > 0 then\
2092 local item = self:getCachedItemDetails(v)\
2093 if item then\
2094 item = Util.shallowCopy(item)\
2095 item.count = v.count\
2096 table.insert(items, item)\
2097 end\
2098 --end\
2099 throttle()\
2100 end\
2101 end)\
2102\
2103 if not s and m then\
2104 _G._syslog(m)\
2105 end\
2106\
2107 itemDB:flush()\
2108 if not Util.empty(items) then\
2109 return items\
2110 end\
2111end\
2112\
2113function RefinedAdapter:getItemInfo(item)\
2114 return self:getItemDetails(item)\
2115end\
2116\
2117function RefinedAdapter:isCPUAvailable()\
2118 return true\
2119end\
2120\
2121function RefinedAdapter:craft(item, qty)\
2122 local detail = self.findItem(item)\
2123 if detail then\
2124 return detail.craft(qty)\
2125 end\
2126end\
2127\
2128function RefinedAdapter:isCrafting(item)\
2129 for _,task in pairs(self.getCraftingTasks()) do\
2130 local output = task.getPattern().outputs[1]\
2131 if output.name == item.name and\
2132 output.damage == item.damage and\
2133 output.nbtHash == item.nbtHash then\
2134 return true\
2135 end\
2136 end\
2137 return false\
2138end\
2139\
2140function RefinedAdapter:provide(item, qty, slot, direction)\
2141 return pcall(function()\
2142 for _,stack in pairs(self.listAvailableItems()) do\
2143 if stack.name == item.name and\
2144 (not item.damage or stack.damage == item.damage) and\
2145 (not item.nbtHash or stack.nbtHash == item.nbtHash) then\
2146 local amount = math.min(qty, stack.count)\
2147 if amount > 0 then\
2148 local detail = self.findItem(item)\
2149 if detail then\
2150 return detail.export(direction or self.direction, amount, slot)\
2151 end\
2152 end\
2153 qty = qty - amount\
2154 if qty <= 0 then\
2155 break\
2156 end\
2157 end\
2158 end\
2159 end)\
2160end\
2161\
2162function RefinedAdapter:extract(slot, qty, toSlot)\
2163 self.pushItems(self.direction, slot, qty, toSlot)\
2164end\
2165\
2166function RefinedAdapter:insert(slot, qty, toSlot)\
2167 self.pullItems(self.direction, slot, qty, toSlot)\
2168end\
2169\
2170return RefinedAdapter",
2171 [ "monitor/mwm.lua" ] = "local Alt = require('opus.alternate')\
2172local Terminal = require('opus.terminal')\
2173local trace = require('opus.trace')\
2174local Util = require('opus.util')\
2175\
2176local colors = _G.colors\
2177local os = _G.os\
2178local peripheral = _G.peripheral\
2179local printError = _G.printError\
2180local shell = _ENV.shell\
2181local term = _G.term\
2182local window = _G.window\
2183\
2184local function syntax()\
2185 error('Syntax:\\nmwm [--config=filename] [monitor]')\
2186end\
2187\
2188local args = Util.parse(...)\
2189local UID = 0\
2190local multishell = { }\
2191local processes = { }\
2192local parentTerm = term.current()\
2193local sessionFile = args.config or 'usr/config/mwm'\
2194local monName = args[1]\
2195local running\
2196local parentMon\
2197\
2198_ENV.multishell = multishell\
2199if monName then\
2200 parentMon = peripheral.wrap(monName) or syntax()\
2201else\
2202 parentMon = peripheral.find('monitor') or syntax()\
2203end\
2204\
2205parentMon.setTextScale(.5)\
2206\
2207local monDim, termDim = { }, { }\
2208monDim.width, monDim.height = parentMon.getSize()\
2209termDim.width, termDim.height = parentTerm.getSize()\
2210\
2211-- even though the monitor window is set to visible\
2212-- the canvas is not (possibly change default in terminal.lua)\
2213\
2214-- canvas is not visible so that redraws\
2215-- are done once in the event loop\
2216local monitor = Terminal.window(parentMon, 1, 1, monDim.width, monDim.height, true)\
2217monitor.setBackgroundColor(colors.gray)\
2218monitor.clear()\
2219\
2220local function nextUID()\
2221 UID = UID + 1\
2222 return UID\
2223end\
2224\
2225local function xprun(env, path, ...)\
2226 setmetatable(env, { __index = _G })\
2227 local fn, m = loadfile(path, env)\
2228 if fn then\
2229 return trace(fn, ...)\
2230 end\
2231 return fn, m\
2232end\
2233\
2234local function write(win, x, y, text)\
2235 win.setCursorPos(x, y)\
2236 win.write(text)\
2237end\
2238\
2239local function redraw()\
2240 --monitor.clear()\
2241 monitor.canvas:dirty()\
2242 --monitor.setBackgroundColor(colors.gray)\
2243 monitor.canvas:clear(colors.gray)\
2244 for k, process in ipairs(processes) do\
2245 process.container.canvas:dirty()\
2246 process:focus(k == #processes)\
2247 end\
2248end\
2249\
2250local function getProcessAt(x, y)\
2251 for k = #processes, 1, -1 do\
2252 local process = processes[k]\
2253 if x >= process.x and\
2254 y >= process.y and\
2255 x <= process.x + process.width - 1 and\
2256 y <= process.y + process.height - 1 then\
2257 return k, process\
2258 end\
2259 end\
2260end\
2261\
2262--[[ A runnable process ]]--\
2263local Process = { }\
2264\
2265function Process:new(env, args)\
2266 args.env = shell.makeEnv(env)\
2267 args.width = args.width or termDim.width\
2268 args.height = args.height or termDim.height\
2269\
2270 -- TODO: randomize start position\
2271 local self = setmetatable({\
2272 uid = nextUID(),\
2273 x = args.x or 1,\
2274 y = args.y or 1,\
2275 width = args.width,\
2276 height = args.height + 1,\
2277 path = args.path,\
2278 args = args.args or { },\
2279 title = args.title or 'shell',\
2280 }, { __index = Process })\
2281\
2282 self:adjustDimensions()\
2283 if not args.x then\
2284 self.x = math.random(1, monDim.width - self.width + 1)\
2285 self.y = math.random(1, monDim.height - self.height + 1)\
2286 end\
2287\
2288 self.container = Terminal.window(monitor, self.x, self.y, self.width, self.height, true)\
2289 self.window = window.create(self.container, 1, 2, args.width, args.height, true)\
2290 self.terminal = self.window\
2291\
2292 self.container.setBackgroundColor(colors.black)\
2293 self.container.clear()\
2294\
2295 self.container.canvas.parent = monitor.canvas\
2296 if not monitor.canvas.children then\
2297 monitor.canvas.children = { }\
2298 end\
2299 table.insert(monitor.canvas.children, 1, self.container.canvas)\
2300 self.container.canvas:setVisible(true)\
2301\
2302 --self.container.getSize = self.window.getSize\
2303\
2304 self.co = coroutine.create(function()\
2305 local result, err\
2306\
2307 if args.fn then\
2308 result, err = Util.runFunction(args.env, args.fn, table.unpack(self.args))\
2309 elseif args.path then\
2310 result, err = xprun(args.env, args.path, table.unpack(self.args))\
2311 end\
2312\
2313 if not result and err and err ~= 'Terminated' then\
2314 printError('\\n' .. tostring(err))\
2315 os.pullEventRaw('terminate')\
2316 end\
2317 multishell.removeProcess(self)\
2318 end)\
2319\
2320 self:focus(false)\
2321\
2322 return self\
2323end\
2324\
2325function Process:drawTitle(focused)\
2326 if self.showSizers and focused then\
2327 local sizers = '\\25 \\26 \\24 \\27'\
2328\
2329 self.container.setBackgroundColor(colors.yellow)\
2330 self.container.setTextColor(colors.black)\
2331\
2332 write(self.container, 1, 1, string.rep(' ', self.width))\
2333 write(self.container, 2, 1, sizers)\
2334\
2335 local str = string.format('%d x %d', self.width, self.height - 1)\
2336 write(self.container, 10, 1, str)\
2337 else\
2338 if focused then\
2339 self.container.setBackgroundColor(colors.yellow)\
2340 else\
2341 self.container.setBackgroundColor(colors.lightGray)\
2342 end\
2343 self.container.setTextColor(colors.black)\
2344 write(self.container, 1, 1, string.rep(' ', self.width))\
2345 write(self.container, 2, 1, self.title)\
2346 end\
2347 write(self.container, self.width - 1, 1, '*')\
2348end\
2349\
2350function Process:focus(focused)\
2351 self:drawTitle(focused)\
2352 if focused then\
2353 self.window.restoreCursor()\
2354 end\
2355end\
2356\
2357function Process:adjustDimensions()\
2358 self.width = math.min(self.width, monDim.width)\
2359 self.height = math.min(self.height, monDim.height)\
2360\
2361 self.x = math.max(1, self.x)\
2362 self.y = math.max(1, self.y)\
2363 self.x = math.min(self.x, monDim.width - self.width + 1)\
2364 self.y = math.min(self.y, monDim.height - self.height + 1)\
2365end\
2366\
2367function Process:reposition()\
2368 self:adjustDimensions()\
2369 self.container.reposition(self.x, self.y, self.width, self.height)\
2370 self.container.setBackgroundColor(colors.black)\
2371 self.container.clear()\
2372 self.window.reposition(1, 2, self.width, self.height - 1)\
2373 if self.window ~= self.terminal then\
2374 self.terminal.reposition(1, 1, self.width, self.height - 1)\
2375 end\
2376 redraw()\
2377end\
2378\
2379function Process:click(x, y)\
2380 if y == 1 then -- title bar\
2381 if x == self.width - 1 then\
2382 self:resume('terminate')\
2383 elseif not self.showSizers then\
2384 self.showSizers = not self.showSizers\
2385 self:drawTitle(true)\
2386 else\
2387 self:resizeClick(x, y)\
2388 end\
2389 elseif x > 1 and x < self.width then\
2390 if self.showSizers then\
2391 self.showSizers = false\
2392 self:drawTitle(true)\
2393 end\
2394 self:resume('mouse_click', 1, x, y - 1)\
2395 self:resume('mouse_up', 1, x, y - 1)\
2396 end\
2397end\
2398\
2399function Process:resizeClick(x)\
2400 if x == 2 then\
2401 self.height = self.height + 1\
2402 elseif x == 6 then\
2403 self.height = self.height - 1\
2404 elseif x == 4 then\
2405 self.width = self.width + 1\
2406 elseif x == 8 then\
2407 self.width = self.width - 1\
2408 else\
2409 return\
2410 end\
2411 self:reposition()\
2412 self:resume('term_resize')\
2413 self:drawTitle(true)\
2414 multishell.saveSession(sessionFile)\
2415end\
2416\
2417function Process:resume(event, ...)\
2418 if coroutine.status(self.co) == 'dead' then\
2419 return\
2420 end\
2421\
2422 if not self.filter or self.filter == event or event == \"terminate\" then\
2423 --term.redirect(self.terminal)\
2424 local previousTerm = term.redirect(self.terminal)\
2425\
2426 local previous = running\
2427 running = self -- stupid shell set title\
2428 local ok, result = coroutine.resume(self.co, event, ...)\
2429 running = previous\
2430\
2431 self.terminal = term.current()\
2432 term.redirect(previousTerm)\
2433\
2434 if ok then\
2435 self.filter = result\
2436 else\
2437 printError(result)\
2438 end\
2439 return ok, result\
2440 end\
2441end\
2442\
2443--[[ Install a multishell manager for the monitor ]]--\
2444function multishell.getFocus()\
2445 return processes[#processes].uid\
2446end\
2447\
2448function multishell.setFocus(uid)\
2449 local process = Util.find(processes, 'uid', uid)\
2450\
2451 if process then\
2452 local lastFocused = processes[#processes]\
2453 if lastFocused ~= process then\
2454\
2455 if lastFocused then\
2456 lastFocused:focus(false)\
2457 end\
2458\
2459 Util.removeByValue(processes, process)\
2460 table.insert(processes, process)\
2461\
2462 process.container.canvas:raise()\
2463 process:focus(true)\
2464 process.container.canvas:dirty()\
2465 end\
2466 return true\
2467 end\
2468 return false\
2469end\
2470\
2471function multishell.getTitle(uid)\
2472 local process = Util.find(processes, 'uid', uid)\
2473 if process then\
2474 return process.title\
2475 end\
2476end\
2477\
2478function multishell.setTitle(uid, title)\
2479 local process = Util.find(processes, 'uid', uid)\
2480 if process then\
2481 process.title = title or ''\
2482 process:focus(process == processes[#processes])\
2483 end\
2484end\
2485\
2486function multishell.getCurrent()\
2487 if running then\
2488 return running.uid\
2489 end\
2490end\
2491\
2492function multishell.getCount()\
2493 return #processes\
2494end\
2495\
2496function multishell.getTabs()\
2497 return processes\
2498end\
2499\
2500function multishell.launch(env, file, ...)\
2501 return multishell.openTab(env, {\
2502 path = file,\
2503 env = env,\
2504 title = 'shell',\
2505 args = { ... },\
2506 })\
2507end\
2508\
2509function multishell.openTab(env, tabInfo)\
2510 local process = Process:new(env, tabInfo)\
2511\
2512 table.insert(processes, 1, process)\
2513\
2514 --local previousTerm = term.current()\
2515 process:resume()\
2516 --term.redirect(previousTerm)\
2517\
2518 multishell.saveSession(sessionFile)\
2519\
2520 return process.uid\
2521end\
2522\
2523function multishell.removeProcess(process)\
2524 Util.removeByValue(processes, process)\
2525 process.container.canvas:removeLayer()\
2526\
2527 multishell.saveSession(sessionFile)\
2528 redraw()\
2529end\
2530\
2531function multishell.saveSession(filename)\
2532 local t = { }\
2533 for _,process in ipairs(processes) do\
2534 if process.path and not process.isShell then\
2535 table.insert(t, {\
2536 x = process.x,\
2537 y = process.y,\
2538 width = process.width,\
2539 height = process.height - 1,\
2540 path = process.path,\
2541 args = process.args,\
2542 })\
2543 end\
2544 end\
2545 Util.writeTable(filename, t)\
2546end\
2547\
2548function multishell.loadSession(filename)\
2549 local config = Util.readTable(filename)\
2550 if config then\
2551 for k = #config, 1, -1 do\
2552 multishell.openTab(_ENV, config[k])\
2553 end\
2554 end\
2555end\
2556\
2557function multishell.stop()\
2558 multishell._stop = true\
2559end\
2560\
2561function multishell.start()\
2562 while not multishell._stop do\
2563\
2564 local event = { os.pullEventRaw() }\
2565\
2566 if event[1] == 'terminate' then\
2567 local focused = processes[#processes]\
2568 if focused.isShell then\
2569 focused:resume('terminate')\
2570 else\
2571 break\
2572 end\
2573\
2574 elseif event[1] == 'monitor_touch' then\
2575 local x, y = event[3], event[4]\
2576\
2577 local key, process = getProcessAt(x, y)\
2578 if process then\
2579 if key ~= #processes then\
2580 multishell.setFocus(process.uid)\
2581 multishell.saveSession(sessionFile)\
2582 end\
2583 process:click(x - process.x + 1, y - process.y + 1)\
2584\
2585 else\
2586 process = processes[#processes]\
2587 if process and process.showSizers then\
2588 process.x = math.floor(x - (process.width) / 2)\
2589 process.y = y\
2590 process:reposition()\
2591 process:drawTitle(true)\
2592 multishell.saveSession(sessionFile)\
2593 end\
2594 end\
2595\
2596 elseif event[1] == 'mouse_click' or\
2597 event[1] == 'mouse_up' then\
2598\
2599 local focused = processes[#processes]\
2600 if not focused.isShell then\
2601 multishell.setFocus(1) -- shell is always 1\
2602 else\
2603 focused:resume(table.unpack(event))\
2604 end\
2605\
2606 elseif event[1] == 'char' or\
2607 event[1] == 'key' or\
2608 event[1] == 'key_up' or\
2609 event[1] == 'paste' then\
2610\
2611 local focused = processes[#processes]\
2612 if focused then\
2613 focused:resume(table.unpack(event))\
2614 end\
2615\
2616 else\
2617 for _,process in pairs(Util.shallowCopy(processes)) do\
2618 process:resume(table.unpack(event))\
2619 end\
2620 end\
2621\
2622 monitor.canvas:render(parentMon)\
2623\
2624 local focused = processes[#processes]\
2625 if focused then\
2626 focused.window.restoreCursor()\
2627 end\
2628 end\
2629end\
2630\
2631--[[ Special shell process for launching programs ]]--\
2632local function addShell()\
2633\
2634 local process = setmetatable({\
2635 x = monDim.width,\
2636 y = monDim.height,\
2637 width = 1,\
2638 height = 1,\
2639 isShell = true,\
2640 uid = nextUID(),\
2641 title = 'Terminal',\
2642 }, { __index = Process })\
2643\
2644 function process:focus(focused)\
2645 self.window.setVisible(focused)\
2646 if focused then\
2647 self.window.restoreCursor()\
2648 else\
2649 parentTerm.clear()\
2650 parentTerm.setCursorBlink(false)\
2651 local str = 'Click screen for shell'\
2652 write(parentTerm,\
2653 math.floor((termDim.width - #str) / 2),\
2654 math.floor(termDim.height / 2),\
2655 str)\
2656 end\
2657 end\
2658\
2659 function process:click()\
2660 end\
2661\
2662 process.container = Terminal.window(monitor, process.x, process.y+1, process.width, process.height, true)\
2663 process.window = window.create(parentTerm, 1, 1, termDim.width, termDim.height, true)\
2664 process.terminal = process.window\
2665\
2666 process.co = coroutine.create(function()\
2667 print('To run a program on the monitor, type \"fg <program>\"')\
2668 print('To quit, type \"exit\"')\
2669 os.run(shell.makeEnv(_ENV), Alt.get('shell'))\
2670 multishell.stop()\
2671 end)\
2672\
2673 table.insert(processes, process)\
2674 process:focus(true)\
2675\
2676 local previousTerm = term.current()\
2677 process:resume()\
2678 term.redirect(previousTerm)\
2679end\
2680\
2681addShell()\
2682\
2683multishell.loadSession(sessionFile)\
2684multishell.start()\
2685\
2686term.redirect(parentTerm)\
2687parentTerm.clear()\
2688parentTerm.setCursorPos(1, 1)",
2689 [ "monitor/mirrorHost.lua" ] = "local Event = require('opus.event')\
2690local Socket = require('opus.socket')\
2691\
2692local colors = _G.colors\
2693local term = _G.term\
2694\
2695local mon = term.current()\
2696local args = { ... }\
2697if args[1] then\
2698 mon = _G.device[args[1]]\
2699end\
2700\
2701if not mon then\
2702 error('Invalid monitor')\
2703end\
2704\
2705mon.setBackgroundColor(colors.black)\
2706mon.clear()\
2707\
2708while true do\
2709 local socket = Socket.server(5902)\
2710\
2711 print('mirror: connection from ' .. socket.dhost)\
2712\
2713 Event.addRoutine(function()\
2714 while true do\
2715 local data = socket:read()\
2716 if not data then\
2717 break\
2718 end\
2719 for _,v in ipairs(data) do\
2720 mon[v.f](unpack(v.args))\
2721 end\
2722 end\
2723 end)\
2724\
2725 while true do\
2726 Event.pullEvent()\
2727 if not socket.connected then\
2728 break\
2729 end\
2730 end\
2731\
2732 print('connection lost')\
2733\
2734 socket:close()\
2735end",
2736 [ "monitor/mirrorClient.lua" ] = "local Event = require('opus.event')\
2737local Socket = require('opus.socket')\
2738local Util = require('opus.util')\
2739\
2740local os = _G.os\
2741\
2742local remoteId\
2743local args = { ... }\
2744if #args == 1 then\
2745 remoteId = tonumber(args[1])\
2746else\
2747 print('Enter host ID')\
2748 remoteId = tonumber(_G.read())\
2749end\
2750\
2751if not remoteId then\
2752 error('Syntax: mirrorClient <host ID>')\
2753end\
2754\
2755local function wrapTerm(socket)\
2756 local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',\
2757 'setTextColor', 'setTextColour', 'setBackgroundColor',\
2758 'setBackgroundColour', 'scroll', 'setCursorBlink', }\
2759\
2760 socket.term = _G.device.terminal\
2761 socket.oldTerm = Util.shallowCopy(socket.term)\
2762\
2763 for _,k in pairs(methods) do\
2764 socket.term[k] = function(...)\
2765 if not socket.queue then\
2766 socket.queue = { }\
2767 Event.onTimeout(0, function()\
2768 if socket.queue then\
2769 socket:write(socket.queue)\
2770 socket.queue = nil\
2771 end\
2772 end)\
2773 end\
2774 table.insert(socket.queue, {\
2775 f = k,\
2776 args = { ... },\
2777 })\
2778 socket.oldTerm[k](...)\
2779 end\
2780 end\
2781end\
2782\
2783while true do\
2784 print('connecting...')\
2785 local socket\
2786\
2787 while true do\
2788 socket = Socket.connect(remoteId, 5902)\
2789 if socket then\
2790 break\
2791 end\
2792 os.sleep(3)\
2793 end\
2794\
2795 print('connected')\
2796\
2797 wrapTerm(socket)\
2798\
2799 os.queueEvent('term_resize')\
2800\
2801 while true do\
2802 local e = Event.pullEvent()\
2803 if e[1] == 'terminate' then\
2804 break\
2805 end\
2806 if not socket.connected then\
2807 break\
2808 end\
2809 end\
2810\
2811 for k,v in pairs(socket.oldTerm) do\
2812 socket.term[k] = v\
2813 end\
2814\
2815 socket:close()\
2816\
2817end",
2818 [ "core/apis/meAdapter18.lua" ] = "local class = require('opus.class')\
2819local RSAdapter = require('core.refinedAdapter')\
2820local Peripheral = require('opus.peripheral')\
2821local Util = require('opus.util')\
2822\
2823local MEAdapter = class(RSAdapter)\
2824\
2825local DEVICE_TYPE = 'appliedenergistics2:interface'\
2826\
2827function MEAdapter:init(args)\
2828 local defaults = {\
2829 name = 'appliedEnergistics',\
2830 jobList = { },\
2831 }\
2832 Util.merge(self, defaults)\
2833 Util.merge(self, args)\
2834\
2835 local controller\
2836 if not self.side then\
2837 controller = Peripheral.getByType(DEVICE_TYPE)\
2838 else\
2839 controller = Peripheral.getBySide(self.side)\
2840 end\
2841\
2842 if controller then\
2843 Util.merge(self, controller)\
2844 end\
2845end\
2846\
2847function MEAdapter:isValid()\
2848 return self.type == DEVICE_TYPE and not not self.findItems\
2849end\
2850\
2851function MEAdapter:clearFinished()\
2852 for _,key in pairs(Util.keys(self.jobList)) do\
2853 local job = self.jobList[key]\
2854 if job.info.status() == 'finished' then\
2855 self.jobList[key] = nil\
2856 end\
2857 end\
2858end\
2859\
2860function MEAdapter:isCPUAvailable()\
2861 local cpus = self.getCraftingCPUs() or { }\
2862 local busy = 0\
2863\
2864 for _,cpu in pairs(cpus) do\
2865 if cpu.busy then\
2866 busy = busy + 1\
2867 end\
2868 end\
2869 self:clearFinished()\
2870 return busy == Util.size(self.jobList) and busy < #cpus\
2871end\
2872\
2873function MEAdapter:craft(item, count)\
2874 if not self:isCPUAvailable() then\
2875 return false\
2876 end\
2877\
2878 local detail = self.findItem(item)\
2879 if detail and detail.craft then\
2880 local info = detail.craft(count or 1)\
2881 if info.status() == 'unknown' then\
2882 self.jobList[info.getId()] = {\
2883 name = item.name,\
2884 damage = item.damage,\
2885 nbtHash = item.nbtHash,\
2886 info = info,\
2887 }\
2888 return true\
2889 end\
2890 return false\
2891 end\
2892end\
2893\
2894function MEAdapter:isCrafting(item)\
2895 self:clearFinished()\
2896\
2897 for _,job in pairs(self.jobList) do\
2898 if job.name == item.name and\
2899 job.damage == item.damage and\
2900 job.nbtHash == item.nbtHash then\
2901 return true\
2902 end\
2903 end\
2904 return false\
2905end\
2906\
2907return MEAdapter",
2908 [ "games/.package" ] = "{\
2909 title = 'Games',\
2910 repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/games',\
2911 description = [[Games by various people]],\
2912 license = 'MIT',\
2913}",
2914 [ "games/etc/fstab" ] = "packages/games/Othello.lua urlfs http://pastebin.com/raw/rMeh1Kag\
2915packages/games/Pipes.lua urlfs https://pastebin.com/raw/skcs9x1s\
2916packages/games/Strafe.lua urlfs https://pastebin.com/raw/jyDH7mLH\
2917packages/games/Breakout.lua urlfs https://gist.github.com/LDDestroier/c7528d95bc0103545c2a/raw\
2918packages/games/Minesweeper.lua urlfs https://pastebin.com/raw/nsKrHTbN\
2919packages/games/Tron.lua urlfs https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua",
2920 [ "common/Follow.lua" ] = "local Event = require('opus.event')\
2921local GPS = require('opus.gps')\
2922local Point = require('opus.point')\
2923local Socket = require('opus.socket')\
2924local Swarm = require('core.swarm')\
2925local UI = require('opus.ui')\
2926local Util = require('opus.util')\
2927\
2928local colors = _G.colors\
2929local network = _G.network\
2930local os = _G.os\
2931\
2932local swarm = Swarm()\
2933local gpt = GPS.getPoint() or error('GPS not found')\
2934local pts, blocks\
2935\
2936local page = UI.Page {\
2937 menuBar = UI.MenuBar {\
2938 buttons = {\
2939 { text = 'Range', event = 'range' },\
2940 { text = 'Stop', event = 'stop' },\
2941 },\
2942 mode = UI.Chooser {\
2943 x = -16,\
2944 choices = {\
2945 { name = 'No breaking', value = 'digNone' },\
2946 { name = 'Destructive', value = 'turtleSafe' },\
2947 },\
2948 value = 'digNone',\
2949 },\
2950 },\
2951 grid = UI.ScrollingGrid {\
2952 y = 2, ey = -2,\
2953 columns = {\
2954 { heading = 'Label', key = 'label' },\
2955 { heading = 'Dist', key = 'distance' },\
2956 { heading = 'Status', key = 'status' },\
2957 { heading = 'Fuel', key = 'fuel' },\
2958 },\
2959 sortColumn = 'distance',\
2960 autospace = true,\
2961 },\
2962 range = UI.SlideOut {\
2963 y = -7, height = 7,\
2964 titleBar = UI.TitleBar {\
2965 event = 'cancel',\
2966 title = 'Enter range',\
2967 },\
2968 notice = UI.TextArea {\
2969 x = 2, ex = -2, y = 3, ey = 4,\
2970 value =\
2971[[Select all turtles within a specified range]],\
2972 },\
2973 entry = UI.TextEntry {\
2974 y = 6, x = 2, ex = 10,\
2975 limit = 4,\
2976 shadowText = 'range',\
2977 accelerators = {\
2978 enter = 'select_range',\
2979 },\
2980 },\
2981 button = UI.Button {\
2982 x = 12, y = 6,\
2983 text = 'Apply',\
2984 event = 'select_range',\
2985 }\
2986 },\
2987}\
2988\
2989function page.grid:getRowTextColor(row, selected)\
2990 if swarm.pool[row.id] then\
2991 return colors.yellow\
2992 end\
2993 return UI.ScrollingGrid.getRowTextColor(self, row, selected)\
2994end\
2995\
2996function page.grid:getDisplayValues(row)\
2997 row = Util.shallowCopy(row)\
2998 if row.fuel then\
2999 row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''\
3000 end\
3001 if row.distance then\
3002 row.distance = Util.round(row.distance, 1)\
3003 end\
3004 return row\
3005end\
3006\
3007function page:enable()\
3008 local function update()\
3009 local t = { }\
3010 for _,v in pairs(network) do\
3011 if v.fuel and v.active and v.fuel > 0 and v.distance then\
3012 table.insert(t, v)\
3013 end\
3014 end\
3015 self.grid:setValues(t)\
3016 end\
3017\
3018 Event.onInterval(3, function()\
3019 update()\
3020 self.grid:draw()\
3021 self:sync()\
3022 end)\
3023\
3024 update()\
3025\
3026 UI.Page.enable(self)\
3027end\
3028\
3029local function follow(member)\
3030 local turtle = member.turtle\
3031 turtle.reset()\
3032 turtle.set({\
3033 digPolicy = page.menuBar.mode.value,\
3034 status = 'Following',\
3035 })\
3036\
3037 if not turtle.enableGPS(nil, true) then\
3038 error('turtle: No GPS found')\
3039 end\
3040\
3041 member.snmp = Socket.connect(member.id, 161)\
3042 member.snmp.co = coroutine.running()\
3043\
3044 local pt\
3045\
3046 while true do\
3047 while pt and Point.same(gpt, pt) do\
3048 os.sleep(.5)\
3049 end\
3050 pt = Point.copy(gpt)\
3051\
3052 local cpt = Point.closest(turtle.getPoint(), pts)\
3053\
3054 turtle.abort(false)\
3055 if turtle.pathfind(cpt, { blocks = blocks }) then\
3056 turtle.headTowards(pt)\
3057 end\
3058 end\
3059end\
3060\
3061function swarm:onRemove(member, status, message)\
3062 if member.socket then\
3063 pcall(function()\
3064 member.turtle.set({ status = 'idle' })\
3065 member.turtle.abort(true)\
3066 end)\
3067 end\
3068 if member.snmp then\
3069 member.snmp:close()\
3070 member.snmp = nil\
3071 end\
3072 if not status then\
3073 _G._syslog(message)\
3074 end\
3075end\
3076\
3077function page:eventHandler(event)\
3078 if event.type == 'grid_select' then\
3079 if not swarm.pool[event.selected.id] then\
3080 swarm:add(event.selected.id)\
3081 swarm:run(follow)\
3082 else\
3083 swarm:remove(event.selected.id)\
3084 end\
3085 self.grid:draw()\
3086\
3087 elseif event.type == 'choice_change' then\
3088 local script = string.format('turtle.set({ digPolicy = \"%s\"})', event.value)\
3089 for _, member in pairs(swarm.pool) do\
3090 member.snmp:write({ type = 'scriptEx', args = script })\
3091 end\
3092\
3093 elseif event.type == 'stop' then\
3094 for id in pairs(swarm.pool) do\
3095 swarm:remove(id)\
3096 end\
3097\
3098 elseif event.type == 'range' then\
3099 self.range:show()\
3100\
3101 elseif event.type == 'cancel' then\
3102 self.range:hide()\
3103\
3104 elseif event.type == 'select_range' then\
3105 local range = tonumber(self.range.entry.value)\
3106\
3107 if range and range > 0 then\
3108 for id, v in pairs(network) do\
3109 if not swarm.pool[id] then\
3110 if v.fuel and v.active and v.fuel > 0 and v.distance and v.distance <= range then\
3111 swarm:add(id)\
3112 end\
3113 end\
3114 end\
3115 swarm:run(follow)\
3116 self.range:hide()\
3117 end\
3118 else\
3119 return UI.Page.eventHandler(self, event)\
3120 end\
3121 return true\
3122end\
3123\
3124Event.addRoutine(function()\
3125 while true do\
3126 local pt = GPS.getPoint()\
3127 if not pts or (pt and not Point.same(pt, gpt)) then\
3128 gpt = pt\
3129 pts = {\
3130 { x = pt.x + 2, z = pt.z, y = pt.y },\
3131 { x = pt.x - 2, z = pt.z, y = pt.y },\
3132 { x = pt.x, z = pt.z + 2, y = pt.y },\
3133 { x = pt.x, z = pt.z - 2, y = pt.y },\
3134 }\
3135 blocks = { }\
3136\
3137 local function addBlocks(tpt)\
3138 table.insert(blocks, tpt)\
3139 local apts = Point.adjacentPoints(tpt)\
3140 for _,apt in pairs(apts) do\
3141 table.insert(blocks, apt)\
3142 end\
3143 end\
3144\
3145 -- don't run into player\
3146 addBlocks(pt)\
3147 addBlocks(Point.above(pt))\
3148\
3149 for _, member in pairs(swarm.pool) do\
3150 if member.snmp then\
3151 member.snmp:write({ type = 'scriptEx', args = 'turtle.abort(true)' })\
3152 end\
3153 end\
3154 end\
3155 os.sleep(1)\
3156 end\
3157end)\
3158\
3159UI:setPage(page)\
3160UI:start()\
3161\
3162swarm:stop()",
3163 [ "core/etc/names/minecraft.json" ] = "{\
3164 \"air\": {\
3165 \"id\": 0,\
3166 \"name\": \"Air\",\
3167 }, \
3168 \"stone\": {\
3169 \"id\": 1, \
3170 \"name\": [\"Stone\",\
3171 \"Granite\",\
3172 \"Polished Granite\",\
3173 \"Diorite\",\
3174 \"Polished Diorite\",\
3175 \"Andesite\",\
3176 \"Polished Andesite\"],\
3177 }, \
3178 \"grass\": {\
3179 \"id\": 2, \
3180 \"name\": \"Grass Block\", \
3181 }, \
3182 \"dirt\": {\
3183 \"id\": 3, \
3184 \"name\": [\"Dirt\",\
3185 \"Coarse Dirt\",\
3186 \"Podzol\"], \
3187 }, \
3188 \"cobblestone\": {\
3189 \"id\": 4, \
3190 \"name\": \"Cobblestone\", \
3191 }, \
3192 \"planks\": {\
3193 \"id\": 5, \
3194 \"name\": [\"Oak Wood Planks\",\
3195 \"Spruce Wood Planks\",\
3196 \"Birch Wood Planks\",\
3197 \"Jungle Wood Planks\",\
3198 \"Acacia Wood Planks\", \
3199 \"Dark Oak Wood Planks\"],\
3200 }, \
3201 \"sapling\": {\
3202 \"id\": 6, \
3203 \"name\": [\"Oak Sapling\",\
3204 \"Spruce Sapling\",\
3205 \"Birch Sapling\",\
3206 \"Jungle Sapling\",\
3207 \"Acacia Sapling\", \
3208 \"Dark Oak Sapling\"],\
3209 \"place\": \"sapling\",\
3210 },\
3211 \"bedrock\": {\
3212 \"id\": 7, \
3213 \"name\": \"Bedrock\", \
3214 }, \
3215 \"flowing_water\": {\
3216 \"id\": 8, \
3217 \"name\": \"Water\",\
3218 \"place\": \"flatten\",\
3219 }, \
3220 \"water\": {\
3221 \"id\": 9, \
3222 \"name\": \"Water\", \
3223 \"place\": \"flatten\",\
3224 }, \
3225 \"flowing_lava\": {\
3226 \"id\": 10, \
3227 \"name\": \"Lava\", \
3228 }, \
3229 \"lava\": {\
3230 \"id\": 11, \
3231 \"name\": \"Lava\", \
3232 }, \
3233 \"sand\": {\
3234 \"id\": 12, \
3235 \"name\": [\"Sand\",\
3236 \"Red Sand\"], \
3237 }, \
3238 \"gravel\": {\
3239 \"id\": 13, \
3240 \"name\": \"Gravel\", \
3241 }, \
3242 \"gold_ore\": {\
3243 \"id\": 14, \
3244 \"name\": \"Gold Ore\", \
3245 }, \
3246 \"iron_ore\": {\
3247 \"id\": 15, \
3248 \"name\": \"Iron Ore\", \
3249 }, \
3250 \"coal_ore\": {\
3251 \"id\": 16, \
3252 \"name\": \"Coal Ore\", \
3253 }, \
3254 \"log\": {\
3255 \"id\": 17, \
3256 \"name\": [\"Oak Wood\",\
3257 \"Spruce Wood\",\
3258 \"Birch Wood\",\
3259 \"Jungle Wood\"], \
3260 \"place\": \"wood\",\
3261 }, \
3262 \"leaves\": {\
3263 \"id\": 18, \
3264 \"name\": [\"Oak Leaves\",\
3265 \"Spruce Leaves\",\
3266 \"Birch Leaves\",\
3267 \"Jungle Leaves\"], \
3268 \"place\": \"leaves\",\
3269 }, \
3270 \"sponge\": {\
3271 \"id\": 19, \
3272 \"name\": [\"Sponge\",\
3273 \"Wet Sponge\"], \
3274 }, \
3275 \"glass\": {\
3276 \"id\": 20, \
3277 \"name\": \"Glass\", \
3278 }, \
3279 \"lapis_ore\": {\
3280 \"id\": 21, \
3281 \"name\": \"Lapis Lazuli Ore\", \
3282 }, \
3283 \"lapis_block\": {\
3284 \"id\": 22, \
3285 \"name\": \"Lapis Lazuli Block\", \
3286 }, \
3287 \"dispenser\": {\
3288 \"id\": 23, \
3289 \"name\": \"Dispenser\", \
3290 \"place\": \"dispenser\",\
3291 }, \
3292 \"sandstone\": {\
3293 \"id\": 24, \
3294 \"name\": [\"Sandstone\",\
3295 \"Chiseled Sandstone\",\
3296 \"Smooth Sandstone\"],\
3297 }, \
3298 \"noteblock\": {\
3299 \"id\": 25, \
3300 \"name\": \"Note Block\", \
3301 }, \
3302 \"bed\": {\
3303 \"id\": 26, \
3304 \"name\": \"Bed\", \
3305 \"place\": \"bed\",\
3306 }, \
3307 \"golden_rail\": {\
3308 \"id\": 27, \
3309 \"name\": \"Powered Rail\", \
3310 \"place\": \"adp-rail\",\
3311 }, \
3312 \"detector_rail\": {\
3313 \"id\": 28, \
3314 \"name\": \"Detector Rail\", \
3315 \"place\": \"adp-rail\",\
3316 }, \
3317 \"sticky_piston\": {\
3318 \"id\": 29, \
3319 \"name\": \"Sticky Piston\", \
3320 \"place\": \"piston\",\
3321 }, \
3322 \"web\": {\
3323 \"id\": 30, \
3324 \"name\": \"Cobweb\", \
3325 }, \
3326 \"tallgrass\": {\
3327 \"id\": 31, \
3328 \"name\": [\"Shrub\",\
3329 \"Grass\",\
3330 \"Fern\"],\
3331 }, \
3332 \"deadbush\": {\
3333 \"id\": 32, \
3334 \"name\": \"Dead Bush\", \
3335 }, \
3336 \"piston\": {\
3337 \"id\": 33, \
3338 \"name\": \"Piston\", \
3339 \"place\": \"piston\",\
3340 }, \
3341 \"piston_head\": {\
3342 \"id\": 34, \
3343 \"name\": \"Piston Extension\", \
3344 \"place\": \"flatten\",\
3345 }, \
3346 \"wool\": {\
3347 \"id\": 35, \
3348 \"name\": [\"White Wool\", \
3349 \"Orange Wool\",\
3350 \"Magenta Wool\",\
3351 \"Light Blue Wool\",\
3352 \"Yellow Wool\",\
3353 \"Lime Wool\",\
3354 \"Pink Wool\",\
3355 \"Gray Wool\",\
3356 \"Light Gray Wool\",\
3357 \"Cyan Wool\",\
3358 \"Purple Wool\",\
3359 \"Blue Wool\",\
3360 \"Brown Wool\",\
3361 \"Green Wool\",\
3362 \"Red Wool\",\
3363 \"Black Wool\"],\
3364 }, \
3365 \"piston_extension\": {\
3366 \"id\": 36, \
3367 \"name\": \"Block moved by Piston\", \
3368 \"place\": \"flatten\",\
3369 }, \
3370 \"yellow_flower\": {\
3371 \"id\": 37, \
3372 \"name\": \"Dandelion\", \
3373 }, \
3374 \"red_flower\": {\
3375 \"id\": 38, \
3376 \"name\": [\"Poppy\",\
3377 \"Blue Orchid\",\
3378 \"Allium\",\
3379 \"Azure Bluet\",\
3380 \"Red Tulip\",\
3381 \"Orange Tulip\",\
3382 \"White Tulip\",\
3383 \"Pink Tulip\",\
3384 \"Oxeye Daisy\"],\
3385 }, \
3386 \"brown_mushroom\": {\
3387 \"id\": 39, \
3388 \"name\": \"Brown Mushroom\", \
3389 }, \
3390 \"red_mushroom\": {\
3391 \"id\": 40, \
3392 \"name\": \"Red Mushroom\", \
3393 }, \
3394 \"gold_block\": {\
3395 \"id\": 41, \
3396 \"name\": \"Block of Gold\", \
3397 }, \
3398 \"iron_block\": {\
3399 \"id\": 42, \
3400 \"name\": \"Block of Iron\", \
3401 }, \
3402 \"double_stone_slab\": {\
3403 \"id\": 43, \
3404 \"name\": \"Double Stone Slab\", \
3405 }, \
3406 \"stone_slab\": {\
3407 \"id\": 44, \
3408 \"name\": [\"Stone Slab\",\
3409 \"Sandstone Slab\",\
3410 \"Wooden Slab\",\
3411 \"Cobblestone Slab\",\
3412 \"Bricks Slab\",\
3413 \"Stone Bricks Slab\",\
3414 \"Nether Brick Slab\",\
3415 \"Quartz Slab\"],\
3416 \"place\": \"slab\",\
3417 }, \
3418 \"brick_block\": {\
3419 \"id\": 45, \
3420 \"name\": \"Bricks\", \
3421 }, \
3422 \"tnt\": {\
3423 \"id\": 46, \
3424 \"name\": \"TNT\", \
3425 }, \
3426 \"bookshelf\": {\
3427 \"id\": 47, \
3428 \"name\": \"Bookshelf\", \
3429 }, \
3430 \"mossy_cobblestone\": {\
3431 \"id\": 48, \
3432 \"name\": \"Moss Stone\", \
3433 }, \
3434 \"obsidian\": {\
3435 \"id\": 49, \
3436 \"name\": \"Obsidian\", \
3437 }, \
3438 \"torch\": {\
3439 \"id\": 50, \
3440 \"name\": \"Torch\", \
3441 \"place\": \"torch\",\
3442 }, \
3443 \"fire\": {\
3444 \"id\": 51, \
3445 \"name\": \"Fire\", \
3446 \"place\": \"flatten\",\
3447 }, \
3448 \"mob_spawner\": {\
3449 \"id\": 52, \
3450 \"name\": \"Monster Spawner\", \
3451 }, \
3452 \"oak_stairs\": {\
3453 \"id\": 53, \
3454 \"name\": \"Oak Wood Stairs\", \
3455 \"place\": \"stairs\",\
3456 }, \
3457 \"chest\": {\
3458 \"id\": 54, \
3459 \"name\": \"Chest\", \
3460 \"place\": \"chest-furnace\",\
3461 }, \
3462 \"redstone_wire\": {\
3463 \"id\": 55, \
3464 \"name\": \"Redstone Wire\", \
3465 \"place\": \"flatten\",\
3466 }, \
3467 \"diamond_ore\": {\
3468 \"id\": 56, \
3469 \"name\": \"Diamond Ore\", \
3470 }, \
3471 \"diamond_block\": {\
3472 \"id\": 57, \
3473 \"name\": \"Block of Diamond\", \
3474 }, \
3475 \"crafting_table\": {\
3476 \"id\": 58, \
3477 \"name\": \"Crafting Table\", \
3478 },\
3479 \"wheat\": {\
3480 \"id\": 59, \
3481 \"name\": \"Wheat\", \
3482 \"place\": \"flatten\",\
3483 }, \
3484 \"farmland\": {\
3485 \"id\": 60, \
3486 \"name\": \"Farmland\", \
3487 \"place\": \"flatten\",\
3488 }, \
3489 \"furnace\": {\
3490 \"id\": 61, \
3491 \"name\": \"Furnace\", \
3492 \"place\": \"chest-furnace\",\
3493 }, \
3494 \"lit_furnace\": {\
3495 \"id\": 62, \
3496 \"name\": \"Burning Furnace\", \
3497 \"place\": \"chest-furnace\",\
3498 }, \
3499 \"standing_sign\": {\
3500 \"id\": 63, \
3501 \"name\": \"Sign\", \
3502 \"place\": \"signpost\",\
3503 }, \
3504 \"wooden_door\": {\
3505 \"id\": 64, \
3506 \"name\": \"Oak Door\", \
3507 \"place\": \"door\",\
3508 }, \
3509 \"ladder\": {\
3510 \"id\": 65, \
3511 \"name\": \"Ladder\", \
3512 \"place\": \"wallsign-ladder\",\
3513 }, \
3514 \"rail\": {\
3515 \"id\": 66, \
3516 \"name\": \"Rail\", \
3517 \"place\": \"rail\",\
3518 }, \
3519 \"stone_stairs\": {\
3520 \"id\": 67, \
3521 \"name\": \"Cobblestone Stairs\", \
3522 \"place\": \"stairs\",\
3523 }, \
3524 \"wall_sign\": {\
3525 \"id\": 68, \
3526 \"name\": \"Sign\", \
3527 \"place\": \"wallsign-ladder\",\
3528 }, \
3529 \"lever\": {\
3530 \"id\": 69, \
3531 \"name\": \"Lever\", \
3532 \"place\": \"lever\",\
3533 }, \
3534 \"stone_pressure_plate\": {\
3535 \"id\": 70, \
3536 \"name\": \"Stone Pressure Plate\", \
3537 }, \
3538 \"iron_door\": {\
3539 \"id\": 71, \
3540 \"name\": \"Iron Door\", \
3541 \"place\": \"door\",\
3542 }, \
3543 \"wooden_pressure_plate\": {\
3544 \"id\": 72, \
3545 \"name\": \"Wooden Pressure Plate\", \
3546 }, \
3547 \"redstone_ore\": {\
3548 \"id\": 73, \
3549 \"name\": \"Redstone Ore\", \
3550 }, \
3551 \"lit_redstone_ore\": {\
3552 \"id\": 74, \
3553 \"name\": \"Redstone Ore\", \
3554 }, \
3555 \"unlit_redstone_torch\": {\
3556 \"id\": 75, \
3557 \"name\": \"Redstone Torch (inactive)\", \
3558 \"place\": \"torch\",\
3559 }, \
3560 \"redstone_torch\": {\
3561 \"id\": 76, \
3562 \"name\": \"Redstone Torch (active)\", \
3563 \"place\": \"torch\",\
3564 }, \
3565 \"stone_button\": {\
3566 \"id\": 77, \
3567 \"name\": \"Stone Button\", \
3568 \"place\": \"button\",\
3569 }, \
3570 \"snow_layer\": {\
3571 \"id\": 78, \
3572 \"name\": \"Snow\", \
3573 \"place\": \"flatten\",\
3574 }, \
3575 \"ice\": {\
3576 \"id\": 79, \
3577 \"name\": \"Ice\", \
3578 }, \
3579 \"snow\": {\
3580 \"id\": 80, \
3581 \"name\": \"Snow\", \
3582 }, \
3583 \"cactus\": {\
3584 \"id\": 81, \
3585 \"name\": \"Cactus\", \
3586 \"place\": \"flatten\",\
3587 }, \
3588 \"clay\": {\
3589 \"id\": 82, \
3590 \"name\": \"Clay\", \
3591 }, \
3592 \"reeds\": {\
3593 \"id\": 83, \
3594 \"name\": \"Sugar Cane\", \
3595 \"place\": \"flatten\",\
3596 }, \
3597 \"jukebox\": {\
3598 \"id\": 84, \
3599 \"name\": \"Jukebox\", \
3600 \"place\": \"flatten\",\
3601 }, \
3602 \"fence\": {\
3603 \"id\": 85, \
3604 \"name\": \"Fence\", \
3605 }, \
3606 \"pumpkin\": {\
3607 \"id\": 86, \
3608 \"name\": \"Pumpkin\", \
3609 \"place\": \"pumpkin\",\
3610 }, \
3611 \"netherrack\": {\
3612 \"id\": 87, \
3613 \"name\": \"Netherrack\", \
3614 }, \
3615 \"soul_sand\": {\
3616 \"id\": 88, \
3617 \"name\": \"Soul Sand\", \
3618 }, \
3619 \"glowstone\": {\
3620 \"id\": 89, \
3621 \"name\": \"Glowstone\", \
3622 }, \
3623 \"portal\": {\
3624 \"id\": 90, \
3625 \"name\": \"Portal\", \
3626 \"place\": \"flatten\",\
3627 }, \
3628 \"lit_pumpkin\": {\
3629 \"id\": 91, \
3630 \"name\": \"Jack o'Lantern\", \
3631 \"place\": \"pumpkin\",\
3632 }, \
3633 \"cake\": {\
3634 \"id\": 92, \
3635 \"name\": \"Cake\", \
3636 \"place\": \"flatten\",\
3637 }, \
3638 \"unpowered_repeater\": {\
3639 \"id\": 93, \
3640 \"name\": \"Redstone Repeater (inactive)\", \
3641 \"place\": \"repeater\",\
3642 }, \
3643 \"powered_repeater\": {\
3644 \"id\": 94, \
3645 \"name\": \"Redstone Repeater (active)\", \
3646 \"place\": \"repeater\",\
3647 }, \
3648 \"stained_glass\": {\
3649 \"id\": 95, \
3650 \"name\": [\"White Stained Glass\", \
3651 \"Orange Stained Glass\",\
3652 \"Magenta Stained Glass\",\
3653 \"Light Blue Stained Glass\",\
3654 \"Yellow Stained Glass\",\
3655 \"Lime Stained Glass\",\
3656 \"Pink Stained Glass\",\
3657 \"Gray Stained Glass\",\
3658 \"Light Gray Stained Glass\",\
3659 \"Cyan Stained Glass\",\
3660 \"Purple Stained Glass\",\
3661 \"Blue Stained Glass\",\
3662 \"Brown Stained Glass\",\
3663 \"Green Stained Glass\",\
3664 \"Red Stained Glass\",\
3665 \"Black Stained Glass\"],\
3666 }, \
3667 \"trapdoor\": {\
3668 \"id\": 96, \
3669 \"name\": \"Trapdoor\", \
3670 \"place\": \"trapdoor\",\
3671 }, \
3672 \"monster_egg\": {\
3673 \"id\": 97, \
3674 \"name\": [\"Stone Monster Egg\", \
3675 \"Cobblestone Monster Egg\", \
3676 \"Stone Brick Monster Egg\", \
3677 \"Mossy Stone Brick Monster Egg\", \
3678 \"Cracked Stone Brick Monster Egg\", \
3679 \"Chiseled Stone Brick Monster Egg\"], \
3680 }, \
3681 \"stonebrick\": {\
3682 \"id\": 98, \
3683 \"name\": [\"Stone Bricks\", \
3684 \"Mossy Stone Bricks\", \
3685 \"Cracked Stone Bricks\", \
3686 \"Chiseled Stone Bricks\"], \
3687 }, \
3688 \"brown_mushroom_block\": {\
3689 \"id\": 99, \
3690 \"name\": \"Brown Mushroom (block)\", \
3691 \"place\": \"flatten\",\
3692 }, \
3693 \"red_mushroom_block\": {\
3694 \"id\": 100, \
3695 \"name\": \"Red Mushroom (block)\", \
3696 \"place\": \"flatten\",\
3697 }, \
3698 \"iron_bars\": {\
3699 \"id\": 101, \
3700 \"name\": \"Iron Bars\", \
3701 }, \
3702 \"glass_pane\": {\
3703 \"id\": 102, \
3704 \"name\": \"Glass Pane\", \
3705 }, \
3706 \"melon_block\": {\
3707 \"id\": 103, \
3708 \"name\": \"Melon\", \
3709 }, \
3710 \"pumpkin_stem\": {\
3711 \"id\": 104, \
3712 \"name\": \"Pumpkin Stem\", \
3713 }, \
3714 \"melon_stem\": {\
3715 \"id\": 105, \
3716 \"name\": \"Melon Stem\", \
3717 }, \
3718 \"vine\": {\
3719 \"id\": 106, \
3720 \"name\": \"Vines\", \
3721 \"place\": \"vine\",\
3722 }, \
3723 \"fence_gate\": {\
3724 \"id\": 107, \
3725 \"name\": \"Fence Gate\", \
3726 \"place\": \"gate\",\
3727 }, \
3728 \"brick_stairs\": {\
3729 \"id\": 108, \
3730 \"name\": \"Brick Stairs\", \
3731 \"place\": \"stairs\",\
3732 }, \
3733 \"stone_brick_stairs\": {\
3734 \"id\": 109, \
3735 \"name\": \"Stone Brick Stairs\", \
3736 \"place\": \"stairs\",\
3737 }, \
3738 \"mycelium\": {\
3739 \"id\": 110, \
3740 \"name\": \"Mycelium\", \
3741 }, \
3742 \"waterlily\": {\
3743 \"id\": 111, \
3744 \"name\": \"Lily Pad\", \
3745 }, \
3746 \"nether_brick\": {\
3747 \"id\": 112, \
3748 \"name\": \"Nether Brick\", \
3749 }, \
3750 \"nether_brick_fence\": {\
3751 \"id\": 113, \
3752 \"name\": \"Nether Brick Fence\", \
3753 }, \
3754 \"nether_brick_stairs\": {\
3755 \"id\": 114, \
3756 \"name\": \"Nether Brick Stairs\", \
3757 \"place\": \"stairs\",\
3758 }, \
3759 \"nether_wart\": {\
3760 \"id\": 115, \
3761 \"name\": \"Nether Wart\", \
3762 \"place\": \"flatten\",\
3763 }, \
3764 \"enchanting_table\": {\
3765 \"id\": 116, \
3766 \"name\": \"Enchantment Table\", \
3767 }, \
3768 \"brewing_stand\": {\
3769 \"id\": 117, \
3770 \"name\": \"Brewing Stand\", \
3771 \"place\": \"flatten\",\
3772 }, \
3773 \"cauldron\": {\
3774 \"id\": 118, \
3775 \"name\": \"Cauldron\", \
3776 \"place\": \"cauldron\",\
3777 }, \
3778 \"end_portal\": {\
3779 \"id\": 119, \
3780 \"name\": \"End Portal\", \
3781 }, \
3782 \"end_portal_frame\": {\
3783 \"id\": 120, \
3784 \"name\": \"End Portal Block\", \
3785 \"place\": \"flatten\",\
3786 }, \
3787 \"end_stone\": {\
3788 \"id\": 121, \
3789 \"name\": \"End Stone\", \
3790 }, \
3791 \"dragon_egg\": {\
3792 \"id\": 122, \
3793 \"name\": \"Dragon Egg\", \
3794 }, \
3795 \"redstone_lamp\": {\
3796 \"id\": 123, \
3797 \"name\": \"Redstone Lamp (inactive)\", \
3798 }, \
3799 \"lit_redstone_lamp\": {\
3800 \"id\": 124, \
3801 \"name\": \"Redstone Lamp (active)\", \
3802 }, \
3803 \"double_wooden_slab\": {\
3804 \"id\": 125, \
3805 \"name\": \"Double Wooden Slab\", \
3806 }, \
3807 \"wooden_slab\": {\
3808 \"id\": 126, \
3809 \"name\": [\"Oak Wood Slab\", \
3810 \"Spruce Wood Slab\", \
3811 \"Birch Wood Slab\", \
3812 \"Jungle Wood Slab\", \
3813 \"Acacia Wood Slab\", \
3814 \"Dark Oak Wood Slab\"], \
3815 \"place\": \"slab\",\
3816 }, \
3817 \"cocoa\": {\
3818 \"id\": 127, \
3819 \"name\": \"Cocoa\", \
3820 \"place\": \"cocoa\",\
3821 }, \
3822 \"sandstone_stairs\": {\
3823 \"id\": 128, \
3824 \"name\": \"Sandstone Stairs\", \
3825 \"place\": \"stairs\",\
3826 }, \
3827 \"emerald_ore\": {\
3828 \"id\": 129, \
3829 \"name\": \"Emerald Ore\", \
3830 }, \
3831 \"ender_chest\": {\
3832 \"id\": 130, \
3833 \"name\": \"Ender Chest\", \
3834 \"place\": \"chest-furnace\",\
3835 }, \
3836 \"tripwire_hook\": {\
3837 \"id\": 131, \
3838 \"name\": \"Tripwire Hook\", \
3839 \"place\": \"tripwire\",\
3840 }, \
3841 \"tripwire\": {\
3842 \"id\": 132, \
3843 \"name\": \"Tripwire\", \
3844 \"place\": \"flatten\",\
3845 }, \
3846 \"emerald_block\": {\
3847 \"id\": 133, \
3848 \"name\": \"Block of Emerald\", \
3849 }, \
3850 \"spruce_stairs\": {\
3851 \"id\": 134, \
3852 \"name\": \"Spruce Wood Stairs\", \
3853 \"place\": \"stairs\",\
3854 }, \
3855 \"birch_stairs\": {\
3856 \"id\": 135, \
3857 \"name\": \"Birch Wood Stairs\", \
3858 \"place\": \"stairs\",\
3859 }, \
3860 \"jungle_stairs\": {\
3861 \"id\": 136, \
3862 \"name\": \"Jungle Wood Stairs\", \
3863 \"place\": \"stairs\",\
3864 }, \
3865 \"command_block\": {\
3866 \"id\": 137, \
3867 \"name\": \"Command Block\", \
3868 }, \
3869 \"beacon\": {\
3870 \"id\": 138, \
3871 \"name\": \"Beacon\", \
3872 }, \
3873 \"cobblestone_wall\": {\
3874 \"id\": 139, \
3875 \"name\": [\"Cobblestone Wall\", \
3876 \"Mossy Cobblestone Wall\"], \
3877 }, \
3878 \"flower_pot\": {\
3879 \"id\": 140, \
3880 \"name\": \"Flower Pot\", \
3881 \"place\": \"flatten\",\
3882 }, \
3883 \"carrots\": {\
3884 \"id\": 141, \
3885 \"name\": \"Carrot\", \
3886 \"place\": \"flatten\",\
3887 }, \
3888 \"potatoes\": {\
3889 \"id\": 142, \
3890 \"name\": \"Potato\", \
3891 \"place\": \"flatten\",\
3892 }, \
3893 \"wooden_button\": {\
3894 \"id\": 143, \
3895 \"name\": \"Wooden Button\", \
3896 \"place\": \"button\",\
3897 }, \
3898 \"skull\": {\
3899 \"id\": 144, \
3900 \"name\": [\"Skeleton Skull\",\
3901 \"Wither Skeleton Skull\",\
3902 \"Zombie Head\",\
3903 \"Human Head\",\
3904 \"Creeper Head\",\
3905 \"Dragon Head\"]\
3906 \"place\": \"mobhead\",\
3907 },\
3908 \"anvil\": {\
3909 \"id\": 145, \
3910 \"name\": [\"Anvil\",\
3911 \"Slightly Damaged Anvil\",\
3912 \"Very Damaged Anvil\"], \
3913 \"place\": \"anvil\",\
3914 }, \
3915 \"trapped_chest\": {\
3916 \"id\": 146, \
3917 \"name\": \"Trapped Chest\", \
3918 \"place\": \"chest-furnace\",\
3919 }, \
3920 \"light_weighted_pressure_plate\": {\
3921 \"id\": 147, \
3922 \"name\": \"Weighted Pressure Plate\", \
3923 }, \
3924 \"heavy_weighted_pressure_plate\": {\
3925 \"id\": 148, \
3926 \"name\": \"Weighted Pressure Plate\", \
3927 }, \
3928 \"unpowered_comparator\": {\
3929 \"id\": 149, \
3930 \"name\": \"Redstone Comparator\", \
3931 \"place\": \"comparator\",\
3932 }, \
3933 \"powered_comparator\": {\
3934 \"id\": 150, \
3935 \"name\": \"Redstone Comparator\", \
3936 }, \
3937 \"daylight_detector\": {\
3938 \"id\": 151, \
3939 \"name\": \"Daylight Sensor\", \
3940 \"place\": \"flatten\",\
3941 }, \
3942 \"redstone_block\": {\
3943 \"id\": 152, \
3944 \"name\": \"Block of Redstone\", \
3945 }, \
3946 \"quartz_ore\": {\
3947 \"id\": 153, \
3948 \"name\": \"Nether Quartz Ore\", \
3949 }, \
3950 \"hopper\": {\
3951 \"id\": 154, \
3952 \"name\": \"Hopper\", \
3953 \"place\": \"hopper\",\
3954 }, \
3955 \"quartz_block\": {\
3956 \"id\": 155, \
3957 \"name\": [\"Block of Quartz\", \
3958 \"Chiseled Quartz Block\", \
3959 \"Pillar Quartz Block\"], \
3960 }, \
3961 \"quartz_stairs\": {\
3962 \"id\": 156, \
3963 \"name\": \"Quartz Stairs\", \
3964 \"place\": \"stairs\",\
3965 }, \
3966 \"activator_rail\": {\
3967 \"id\": 157, \
3968 \"name\": \"Activator Rail\", \
3969 \"place\": \"adp-rail\",\
3970 }, \
3971 \"dropper\": {\
3972 \"id\": 158, \
3973 \"name\": \"Dropper\", \
3974 \"place\": \"dispenser\",\
3975 }, \
3976 \"stained_hardened_clay\": {\
3977 \"id\": 159, \
3978 \"name\": [\"White Stained Clay\", \
3979 \"Orange Stained Clay\", \
3980 \"Magenta Stained Clay\", \
3981 \"Light Blue Stained Clay\", \
3982 \"Yellow Stained Clay\", \
3983 \"Lime Stained Clay\", \
3984 \"Pink Stained Clay\", \
3985 \"Gray Stained Clay\", \
3986 \"Light Gray Stained Clay\", \
3987 \"Cyan Stained Clay\", \
3988 \"Purple Stained Clay\", \
3989 \"Blue Stained Clay\", \
3990 \"Brown Stained Clay\", \
3991 \"Green Stained Clay\", \
3992 \"Red Stained Clay\", \
3993 \"Black Stained Clay\"], \
3994 }, \
3995 \"stained_glass_pane\": {\
3996 \"id\": 160, \
3997 \"name\": [\"White Stained Glass Pane\", \
3998 \"Orange Stained Glass Pane\",\
3999 \"Magenta Stained Glass Pane\",\
4000 \"Light Blue Stained Glass Pane\",\
4001 \"Yellow Stained Glass Pane\",\
4002 \"Lime Stained Glass Pane\",\
4003 \"Pink Stained Glass Pane\",\
4004 \"Gray Stained Glass Pane\",\
4005 \"Light Gray Stained Glass Pane\",\
4006 \"Cyan Stained Glass Pane\",\
4007 \"Purple Stained Glass Pane\",\
4008 \"Blue Stained Glass Pane\",\
4009 \"Brown Stained Glass Pane\",\
4010 \"Green Stained Glass Pane\",\
4011 \"Red Stained Glass Pane\",\
4012 \"Black Stained Glass Pane\"],\
4013 }, \
4014 \"leaves2\": {\
4015 \"id\": 161, \
4016 \"name\": [\"Acacia Leaves\", \
4017 \"Dark Oak Leaves\"],\
4018 \"place\": \"leaves\",\
4019 }, \
4020 \"log2\": {\
4021 \"id\": 162, \
4022 \"name\": [\"Acacia Wood\", \
4023 \"Dark Oak Wood\"],\
4024 \"place\": \"wood\",\
4025 }, \
4026 \"acacia_stairs\": {\
4027 \"id\": 163, \
4028 \"name\": \"Acacia Wood Stairs\", \
4029 \"place\": \"stairs\",\
4030 }, \
4031 \"dark_oak_stairs\": {\
4032 \"id\": 164, \
4033 \"name\": \"Dark Oak Wood Stairs\", \
4034 \"place\": \"stairs\",\
4035 }, \
4036 \"slime\": {\
4037 \"id\": 165, \
4038 \"name\": \"Slime Block\", \
4039 }, \
4040 \"barrier\": {\
4041 \"id\": 166, \
4042 \"name\": \"Barrier\", \
4043 }, \
4044 \"iron_trapdoor\": {\
4045 \"id\": 167, \
4046 \"name\": \"Iron Trapdoor\", \
4047 \"place\": \"trapdoor\",\
4048 }, \
4049 \"prismarine\": {\
4050 \"id\": 168, \
4051 \"name\": [\"Prismarine\", \
4052 \"Prismarine Bricks\",\
4053 \"Dark Prismarine\"],\
4054 }, \
4055 \"sea_lantern\": {\
4056 \"id\": 169, \
4057 \"name\": \"Sea Lantern\", \
4058 }, \
4059 \"hay_block\": {\
4060 \"id\": 170, \
4061 \"name\": \"Hay Block\", \
4062 \"place\": \"hay-bale\",\
4063 }, \
4064 \"carpet\": {\
4065 \"id\": 171, \
4066 \"name\": [\"Carpet\", \
4067 \"Orange Carpet\",\
4068 \"Magenta Carpet\",\
4069 \"Light Blue Carpet\",\
4070 \"Yellow Carpet\",\
4071 \"Lime Carpet\",\
4072 \"Pink Carpet\",\
4073 \"Gray Carpet\",\
4074 \"Light Gray Carpet\",\
4075 \"Cyan Carpet\",\
4076 \"Purple Carpet\",\
4077 \"Blue Carpet\",\
4078 \"Brown Carpet\",\
4079 \"Green Carpet\",\
4080 \"Red Carpet\",\
4081 \"Black Carpet\"],\
4082 }, \
4083 \"hardened_clay\": {\
4084 \"id\": 172, \
4085 \"name\": \"Hardened Clay\", \
4086 }, \
4087 \"coal_block\": {\
4088 \"id\": 173, \
4089 \"name\": \"Block of Coal\", \
4090 }, \
4091 \"packed_ice\": {\
4092 \"id\": 174, \
4093 \"name\": \"Packed Ice\", \
4094 }, \
4095 \"double_plant\": {\
4096 \"id\": 175, \
4097 \"name\": [\"Sunflower\",\
4098 \"Lilac\",\
4099 \"Double Tallgrass\",\
4100 \"Large Fern\",\
4101 \"Rose Bush\",\
4102 \"Peony\"],\
4103 \"place\": \"largeplant\",\
4104 }, \
4105 \"standing_banner\": {\
4106 \"id\": 176, \
4107 \"name\": \"Banner\", \
4108 \"place\": \"signpost\",\
4109 }, \
4110 \"wall_banner\": {\
4111 \"id\": 177, \
4112 \"name\": \"Banner\", \
4113 \"place\": \"wallsign-ladder\",\
4114 }, \
4115 \"daylight_detector_inverted\": {\
4116 \"id\": 178, \
4117 \"name\": \"Inverted Daylight Sensor\", \
4118 \"place\": \"flatten\",\
4119 }, \
4120 \"red_sandstone\": {\
4121 \"id\": 179, \
4122 \"name\": [\"Red Sandstone\", \
4123 \"Chiseled Red Sandstone\",\
4124 \"Smooth Red Sandstone\"],\
4125 }, \
4126 \"red_sandstone_stairs\": {\
4127 \"id\": 180, \
4128 \"name\": \"Red Sandstone Stairs\", \
4129 \"place\": \"stairs\",\
4130 }, \
4131 \"double_stone_slab2\": {\
4132 \"id\": 181, \
4133 \"name\": \"Double Red Sandstone Slab\", \
4134 }, \
4135 \"stone_slab2\": {\
4136 \"id\": 182, \
4137 \"name\": \"Red Sandstone Slab\", \
4138 \"place\": \"slab\",\
4139 }, \
4140 \"spruce_fence_gate\": {\
4141 \"id\": 183, \
4142 \"name\": \"Spruce Fence Gate\", \
4143 \"place\": \"gate\",\
4144 }, \
4145 \"birch_fence_gate\": {\
4146 \"id\": 184, \
4147 \"name\": \"Birch Fence Gate\", \
4148 \"place\": \"gate\",\
4149 }, \
4150 \"jungle_fence_gate\": {\
4151 \"id\": 185, \
4152 \"name\": \"Jungle Fence Gate\", \
4153 \"place\": \"gate\",\
4154 }, \
4155 \"dark_oak_fence_gate\": {\
4156 \"id\": 186, \
4157 \"name\": \"Dark Oak Fence Gate\", \
4158 \"place\": \"gate\",\
4159 }, \
4160 \"acacia_fence_gate\": {\
4161 \"id\": 187, \
4162 \"name\": \"Acacia Fence Gate\", \
4163 \"place\": \"gate\",\
4164 }, \
4165 \"spruce_fence\": {\
4166 \"id\": 188, \
4167 \"name\": \"Spruce Fence\", \
4168 }, \
4169 \"birch_fence\": {\
4170 \"id\": 189, \
4171 \"name\": \"Birch Fence\", \
4172 }, \
4173 \"jungle_fence\": {\
4174 \"id\": 190, \
4175 \"name\": \"Jungle Fence\", \
4176 }, \
4177 \"dark_oak_fence\": {\
4178 \"id\": 191, \
4179 \"name\": \"Dark Oak Fence\", \
4180 }, \
4181 \"acacia_fence\": {\
4182 \"id\": 192, \
4183 \"name\": \"Acacia Fence\", \
4184 }, \
4185 \"spruce_door\": {\
4186 \"id\": 193, \
4187 \"name\": \"Spruce Door\", \
4188 \"place\": \"door\",\
4189 }, \
4190 \"birch_door\": {\
4191 \"id\": 194, \
4192 \"name\": \"Birch Door\", \
4193 \"place\": \"door\",\
4194 }, \
4195 \"jungle_door\": {\
4196 \"id\": 195, \
4197 \"name\": \"Jungle Door\", \
4198 \"place\": \"door\",\
4199 }, \
4200 \"acacia_door\": {\
4201 \"id\": 196, \
4202 \"name\": \"Acacia Door\", \
4203 \"place\": \"door\",\
4204 }, \
4205 \"dark_oak_door\": {\
4206 \"id\": 197, \
4207 \"name\": \"Dark Oak Door\", \
4208 \"place\": \"door\",\
4209 },\
4210 \"end_rod\": {\
4211 \"id\": 198\
4212 \"name\": \"End Rod\",\
4213 \"place\": \"end_rod\",\
4214 },\
4215 \"chorus_plant\": {\
4216 \"id\": 199\
4217 \"name\": \"Chorus Plant\",\
4218 },\
4219 \"chorus_flower\": {\
4220 \"id\": 200\
4221 \"name\": \"Chorus Flower\",\
4222 },\
4223 \"purpur_block\": {\
4224 \"id\": 201\
4225 \"name\": \"Purpur Block\",\
4226 },\
4227 \"purpur_pillar\": {\
4228 \"id\": 202\
4229 \"name\": \"Purpur Pillar\",\
4230 },\
4231 \"purpur_stairs\": {\
4232 \"id\": 203\
4233 \"name\": \"Purpur Stairs\",\
4234 },\
4235 \"purpur_double_slab\": {\
4236 \"id\": 204\
4237 \"name\": \"Double Purpur Slabs\",\
4238 },\
4239 \"purpur_slab\": {\
4240 \"name\": \"Purpur Slab\",\
4241 \"id\": 205,\
4242 \"place\": \"slab\",\
4243 },\
4244 \"end_bricks\": {\
4245 \"name\": \"End Stone Bricks\",\
4246 \"id\": 206\
4247 },\
4248 \"beetroots\": {\
4249 \"name\": \"Beetroot\",\
4250 \"id\": 207\
4251 },\
4252 \"grass_path\": {\
4253 \"name\": \"Path\",\
4254 \"id\": 208\
4255 },\
4256 \"end_gateway\": {\
4257 \"id\": 209,\
4258 \"name\": \"End Gateway\"\
4259 },\
4260 \"repeating_command_block\": {\
4261 \"name\": \"Repeating Command Block\",\
4262 \"id\": 210,\
4263 \"place\": \"flatten\",\
4264 },\
4265 \"chain_command_block\": {\
4266 \"name\": \"Chain Command Block\",\
4267 \"id\": 211\
4268 },\
4269 \"frosted_ice\": {\
4270 \"name\": \"Frosted Ice\",\
4271 \"id\": 212\
4272 },\
4273 \"magma\": {\
4274 \"id\": 213,\
4275 \"name\": \"Magma Block\",\
4276 },\
4277 \"nether_wart_block\": {\
4278 \"id\": 214,\
4279 \"name\": \"Nether Wart Block\",\
4280 },\
4281 \"red_nether_brick\": {\
4282 \"id\": 215,\
4283 \"name\": \"Red Nether Brick\",\
4284 },\
4285 \"bone_block\": {\
4286 \"id\": 216,\
4287 \"name\": \"Bone Block\",\
4288 },\
4289 \"structure_void\": {\
4290 \"id\": 217,\
4291 \"name\": \"Structure Void\",\
4292 },\
4293 \"observer\": {\
4294 \"name\": \"Observer\",\
4295 \"id\": 218\
4296 },\
4297 \"white_shulker_box\": {\
4298 \"name\": \"White Shulker Box\",\
4299 \"id\": 219\
4300 },\
4301 \"orange_shulker_box\": {\
4302 \"name\": \"Orange Shulker Box\",\
4303 \"id\": 220\
4304 },\
4305 \"magenta_shulker_box\": {\
4306 \"name\": \"Magenta Shulker Box\",\
4307 \"id\": 221\
4308 },\
4309 \"light_blue_shulker_box\": {\
4310 \"name\": \"Light Blue Shulker Box\",\
4311 \"id\": 222\
4312 },\
4313 \"yellow_shulker_box\": {\
4314 \"name\": \"Yellow Shulker Box\",\
4315 \"id\": 223\
4316 },\
4317 \"lime_shulker_box\": {\
4318 \"name\": \"Lime Shulker Box\",\
4319 \"id\": 224\
4320 },\
4321 \"pink_shulker_box\": {\
4322 \"name\": \"Pink Shulker Box\",\
4323 \"id\": 225\
4324 },\
4325 \"gray_shulker_box\": {\
4326 \"name\": \"Gray Shulker Box\",\
4327 \"id\": 226\
4328 },\
4329 \"silver_shulker_box\": {\
4330 \"name\": \"Light Gray Shulker Box\",\
4331 \"id\": 227\
4332 },\
4333 \"cyan_shulker_box\": {\
4334 \"name\": \"Cyan Shulker Box\",\
4335 \"id\": 228\
4336 },\
4337 \"purple_shulker_box\": {\
4338 \"name\": \"Purple Shulker Box\",\
4339 \"id\": 229\
4340 },\
4341 \"blue_shulker_box\": {\
4342 \"name\": \"Blue Shulker Box\",\
4343 \"id\": 230\
4344 },\
4345 \"brown_shulker_box\": {\
4346 \"name\": \"Brown Shulker Box\",\
4347 \"id\": 231\
4348 },\
4349 \"green_shulker_box\": {\
4350 \"name\": \"Green Shulker Box\",\
4351 \"id\": 232\
4352 },\
4353 \"red_shulker_box\": {\
4354 \"name\": \"Red Shulker Box\",\
4355 \"id\": 233\
4356 },\
4357 \"black_shulker_box\": {\
4358 \"name\": \"Black Shulker Box\",\
4359 \"id\": 234\
4360 },\
4361 \"white_glazed_terracotta\": {\
4362 \"id\": 235,\
4363 \"name\": \"White glazed terracotta\",\
4364 },\
4365 \"orange_glazed_terracotta\": {\
4366 \"id\": 236,\
4367 \"name\": \"Orange glazed terracotta\",\
4368 },\
4369 \"magenta_glazed_terracotta\": {\
4370 \"id\": 237,\
4371 \"name\": \"Magenta glazed terracotta\",\
4372 },\
4373 \"light_blue_glazed_terracotta\": {\
4374 \"id\": 238,\
4375 \"name\": \"Light blue glazed terracotta\",\
4376 },\
4377 \"yellow_glazed_terracotta\": {\
4378 \"id\": 239,\
4379 \"name\": \"Yellow glazed terracotta\",\
4380 },\
4381 \"lime_glazed_terracotta\": {\
4382 \"id\": 240,\
4383 \"name\": \"Lime glazed terracotta\",\
4384 },\
4385 \"pink_glazed_terracotta\": {\
4386 \"id\": 241,\
4387 \"name\": \"Pink glazed terracotta\",\
4388 },\
4389 \"gray_glazed_terracotta\": {\
4390 \"id\": 242,\
4391 \"name\": \"Gray glazed terracotta\",\
4392 },\
4393 \"light_gray_glazed_terracotta\": {\
4394 \"id\": 243,\
4395 \"name\": \"Light gray glazed terracotta\",\
4396 },\
4397 \"cyan_glazed_terracotta\": {\
4398 \"id\": 244,\
4399 \"name\": \"Cyan glazed terracotta\",\
4400 },\
4401 \"purple_glazed_terracotta\": {\
4402 \"id\": 245,\
4403 \"name\": \"Purple glazed terracotta\",\
4404 },\
4405 \"blue_glazed_terracotta\": {\
4406 \"id\": 246,\
4407 \"name\": \"Blue glazed terracotta\",\
4408 },\
4409 \"brown_glazed_terracotta\": {\
4410 \"id\": 247,\
4411 \"name\": \"Brown glazed terracotta\",\
4412 },\
4413 \"green_glazed_terracotta\": {\
4414 \"id\": 248,\
4415 \"name\": \"Green glazed terracotta\",\
4416 },\
4417 \"red_glazed_terracotta\": {\
4418 \"id\": 249,\
4419 \"name\": \"Red glazed terracotta\",\
4420 },\
4421 \"black_glazed_terracotta\": {\
4422 \"id\": 250,\
4423 \"name\": \"Black glazed terracotta\",\
4424 },\
4425 \"concrete\": {\
4426 \"id\": 251,\
4427 \"name\": [\"White concrete\",\
4428 \"Orange concrete\",\
4429 \"Magenta concrete\",\
4430 \"Light blue concrete\",\
4431 \"Yellow concrete\",\
4432 \"Lime concrete\",\
4433 \"Pink concrete\",\
4434 \"Gray concrete\",\
4435 \"Silver concrete\",\
4436 \"Cyan concrete\",\
4437 \"Purple concrete\",\
4438 \"Blue concrete\",\
4439 \"Brown concrete\",\
4440 \"Green concrete\",\
4441 \"Red concrete\",\
4442 \"Black concrete\"],\
4443 },\
4444 \"concrete_powder\": {\
4445 \"id\": 252,\
4446 \"name\": [\"White concrete powder\",\
4447 \"Orange concrete powder\",\
4448 \"Magenta concrete powder\",\
4449 \"Light blue concrete powder\",\
4450 \"Yellow concrete powder\",\
4451 \"Lime concrete powder\",\
4452 \"Pink concrete powder\",\
4453 \"Gray concrete powder\",\
4454 \"Silver concrete powder\",\
4455 \"Cyan concrete powder\",\
4456 \"Purple concrete powder\",\
4457 \"Blue concrete powder\",\
4458 \"Brown concrete powder\",\
4459 \"Green concrete powder\",\
4460 \"Red concrete powder\",\
4461 \"Black concrete powder\"],\
4462 },\
4463 \"structure_block\": {\
4464 \"name\": [\"Structure Block (Save)\", \
4465 \"Structure Block (Load)\",\
4466 \"Structure Block (Corner)\",\
4467 \"Structure Block (Data)\"],\
4468 \"id\": 255\
4469 },\
4470 \"iron_shovel\": {\
4471 \"id\": 256,\
4472 \"name\": \"Iron Shovel\"\
4473 },\
4474 \"iron_pickaxe\": {\
4475 \"id\": 257,\
4476 \"name\": \"Iron Pickaxe\"\
4477 },\
4478 \"iron_axe\": {\
4479 \"id\": 258,\
4480 \"name\": \"Iron Axe\"\
4481 },\
4482 \"flint_and_steel\": {\
4483 \"id\": 259,\
4484 \"name\": \"Flint and Steel\"\
4485 },\
4486 \"apple\": {\
4487 \"id\": 260,\
4488 \"name\": \"Apple\"\
4489 },\
4490 \"bow\": {\
4491 \"id\": 261,\
4492 \"name\": \"Bow\"\
4493 },\
4494 \"arrow\": {\
4495 \"id\": 262,\
4496 \"name\": \"Arrow\"\
4497 },\
4498 \"coal\": {\
4499 \"name\": [\"Coal\", \
4500 \"Charcoal\"],\
4501 \"id\": 263\
4502 },\
4503 \"diamond\": {\
4504 \"id\": 264,\
4505 \"name\": \"Diamond\"\
4506 },\
4507 \"iron_ingot\": {\
4508 \"id\": 265,\
4509 \"name\": \"Iron Ingot\"\
4510 },\
4511 \"gold_ingot\": {\
4512 \"id\": 266,\
4513 \"name\": \"Gold Ingot\"\
4514 },\
4515 \"iron_sword\": {\
4516 \"id\": 267,\
4517 \"name\": \"Iron Sword\"\
4518 },\
4519 \"wooden_sword\": {\
4520 \"id\": 268,\
4521 \"name\": \"Wooden Sword\"\
4522 },\
4523 \"wooden_shovel\": {\
4524 \"id\": 269,\
4525 \"name\": \"Wooden Shovel\"\
4526 },\
4527 \"wooden_pickaxe\": {\
4528 \"id\": 270,\
4529 \"name\": \"Wooden Pickaxe\"\
4530 },\
4531 \"wooden_axe\": {\
4532 \"id\": 271,\
4533 \"name\": \"Wooden Axe\"\
4534 },\
4535 \"stone_sword\": {\
4536 \"id\": 272,\
4537 \"name\": \"Stone Sword\"\
4538 },\
4539 \"stone_shovel\": {\
4540 \"id\": 273,\
4541 \"name\": \"Stone Shovel\"\
4542 },\
4543 \"stone_pickaxe\": {\
4544 \"id\": 274,\
4545 \"name\": \"Stone Pickaxe\"\
4546 },\
4547 \"stone_axe\": {\
4548 \"id\": 275,\
4549 \"name\": \"Stone Axe\"\
4550 },\
4551 \"diamond_sword\": {\
4552 \"id\": 276,\
4553 \"name\": \"Diamond Sword\"\
4554 },\
4555 \"diamond_shovel\": {\
4556 \"id\": 277,\
4557 \"name\": \"Diamond Shovel\"\
4558 },\
4559 \"diamond_pickaxe\": {\
4560 \"id\": 278,\
4561 \"name\": \"Diamond Pickaxe\"\
4562 },\
4563 \"diamond_axe\": {\
4564 \"id\": 279,\
4565 \"name\": \"Diamond Axe\"\
4566 },\
4567 \"stick\": {\
4568 \"id\": 280,\
4569 \"name\": \"Stick\"\
4570 },\
4571 \"bowl\": {\
4572 \"id\": 281,\
4573 \"name\": \"Bowl\"\
4574 },\
4575 \"mushroom_stew\": {\
4576 \"id\": 282,\
4577 \"name\": \"Mushroom Stew\"\
4578 },\
4579 \"golden_sword\": {\
4580 \"id\": 283,\
4581 \"name\": \"Golden Sword\"\
4582 },\
4583 \"golden_shovel\": {\
4584 \"id\": 284,\
4585 \"name\": \"Golden Shovel\"\
4586 },\
4587 \"golden_pickaxe\": {\
4588 \"id\": 285,\
4589 \"name\": \"Golden Pickaxe\"\
4590 },\
4591 \"golden_axe\": {\
4592 \"id\": 286,\
4593 \"name\": \"Golden Axe\"\
4594 },\
4595 \"string\": {\
4596 \"id\": 287,\
4597 \"name\": \"String\"\
4598 },\
4599 \"feather\": {\
4600 \"id\": 288,\
4601 \"name\": \"Feather\"\
4602 },\
4603 \"gunpowder\": {\
4604 \"id\": 289,\
4605 \"name\": \"Gunpowder\"\
4606 },\
4607 \"wooden_hoe\": {\
4608 \"id\": 290,\
4609 \"name\": \"Wooden Hoe\"\
4610 },\
4611 \"stone_hoe\": {\
4612 \"id\": 291,\
4613 \"name\": \"Stone Hoe\"\
4614 },\
4615 \"iron_hoe\": {\
4616 \"id\": 292,\
4617 \"name\": \"Iron Hoe\"\
4618 },\
4619 \"diamond_hoe\": {\
4620 \"id\": 293,\
4621 \"name\": \"Diamond Hoe\"\
4622 },\
4623 \"golden_hoe\": {\
4624 \"id\": 294,\
4625 \"name\": \"Golden Hoe\"\
4626 },\
4627 \"wheat_seeds\": {\
4628 \"id\": 295,\
4629 \"name\": \"Wheat Seeds\",\
4630 },\
4631 \"bread\": {\
4632 \"id\": 297,\
4633 \"name\": \"Bread\"\
4634 },\
4635 \"leather_helmet\": {\
4636 \"id\": 298,\
4637 \"name\": \"Leather Helmet\"\
4638 },\
4639 \"leather_chestplate\": {\
4640 \"id\": 299,\
4641 \"name\": \"Leather Tunic\"\
4642 },\
4643 \"leather_leggings\": {\
4644 \"id\": 300,\
4645 \"name\": \"Leather Pants\"\
4646 },\
4647 \"leather_boots\": {\
4648 \"id\": 301,\
4649 \"name\": \"Leather Boots\"\
4650 },\
4651 \"chainmail_helmet\": {\
4652 \"id\": 302,\
4653 \"name\": \"Chainmail Helmet\"\
4654 },\
4655 \"chainmail_chestplate\": {\
4656 \"id\": 303,\
4657 \"name\": \"Chainmail Chestplate\"\
4658 },\
4659 \"chainmail_leggings\": {\
4660 \"id\": 304,\
4661 \"name\": \"Chainmail Leggings\"\
4662 },\
4663 \"chainmail_boots\": {\
4664 \"id\": 305,\
4665 \"name\": \"Chainmail Boots\"\
4666 },\
4667 \"iron_helmet\": {\
4668 \"id\": 306,\
4669 \"name\": \"Iron Helmet\"\
4670 },\
4671 \"iron_chestplate\": {\
4672 \"id\": 307,\
4673 \"name\": \"Iron Chestplate\"\
4674 },\
4675 \"iron_leggings\": {\
4676 \"id\": 308,\
4677 \"name\": \"Iron Leggings\"\
4678 },\
4679 \"iron_boots\": {\
4680 \"id\": 309,\
4681 \"name\": \"Iron Boots\"\
4682 },\
4683 \"diamond_helmet\": {\
4684 \"id\": 310,\
4685 \"name\": \"Diamond Helmet\"\
4686 },\
4687 \"diamond_chestplate\": {\
4688 \"id\": 311,\
4689 \"name\": \"Diamond Chestplate\"\
4690 },\
4691 \"diamond_leggings\": {\
4692 \"id\": 312,\
4693 \"name\": \"Diamond Leggings\"\
4694 },\
4695 \"diamond_boots\": {\
4696 \"id\": 313,\
4697 \"name\": \"Diamond Boots\"\
4698 },\
4699 \"golden_helmet\": {\
4700 \"id\": 314,\
4701 \"name\": \"Golden Helmet\"\
4702 },\
4703 \"golden_chestplate\": {\
4704 \"id\": 315,\
4705 \"name\": \"Golden Chestplate\"\
4706 },\
4707 \"golden_leggings\": {\
4708 \"id\": 316,\
4709 \"name\": \"Golden Leggings\"\
4710 },\
4711 \"golden_boots\": {\
4712 \"id\": 317,\
4713 \"name\": \"Golden Boots\"\
4714 },\
4715 \"flint\": {\
4716 \"id\": 318,\
4717 \"name\": \"Flint\"\
4718 },\
4719 \"porkchop\": {\
4720 \"id\": 319,\
4721 \"name\": \"Raw Porkchop\"\
4722 },\
4723 \"cooked_porkchop\": {\
4724 \"id\": 320,\
4725 \"name\": \"Cooked Porkchop\"\
4726 },\
4727 \"painting\": {\
4728 \"id\": 321,\
4729 \"name\": \"Painting\"\
4730 },\
4731 \"golden_apple\": {\
4732 \"id\": 322,\
4733 \"name\": [\"Enchanted Golden Apple\",\
4734 \"Enchanted Golden Apple\" ]\
4735 },\
4736 \"sign\": {\
4737 \"id\": 323,\
4738 \"name\": \"Sign\",\
4739 },\
4740 \"bucket\": {\
4741 \"id\": 325,\
4742 \"name\": \"Bucket\"\
4743 },\
4744 \"water_bucket\": {\
4745 \"id\": 326,\
4746 \"name\": \"Water Bucket\"\
4747 },\
4748 \"lava_bucket\": {\
4749 \"id\": 327,\
4750 \"name\": \"Lava Bucket\"\
4751 },\
4752 \"minecart\": {\
4753 \"id\": 328,\
4754 \"name\": \"Minecart\"\
4755 },\
4756 \"saddle\": {\
4757 \"id\": 329,\
4758 \"name\": \"Saddle\"\
4759 },\
4760 \"redstone\": {\
4761 \"id\": 331,\
4762 \"name\": \"Redstone Dust\",\
4763 },\
4764 \"snowball\": {\
4765 \"id\": 332,\
4766 \"name\": \"Snowball\"\
4767 },\
4768 \"boat\": {\
4769 \"id\": 333,\
4770 \"name\": \"Oak Boat\"\
4771 },\
4772 \"leather\": {\
4773 \"id\": 334,\
4774 \"name\": \"Leather\"\
4775 },\
4776 \"milk_bucket\": {\
4777 \"id\": 335,\
4778 \"name\": \"Milk Bucket\"\
4779 },\
4780 \"brick\": {\
4781 \"id\": 336,\
4782 \"name\": \"Brick\"\
4783 },\
4784 \"clay_ball\": {\
4785 \"id\": 337,\
4786 \"name\": \"Clay\"\
4787 },\
4788 \"paper\": {\
4789 \"id\": 339,\
4790 \"name\": \"Paper\"\
4791 },\
4792 \"book\": {\
4793 \"id\": 340,\
4794 \"name\": \"Book\"\
4795 },\
4796 \"slime_ball\": {\
4797 \"id\": 341,\
4798 \"name\": \"Slimeball\"\
4799 },\
4800 \"chest_minecart\": {\
4801 \"id\": 342,\
4802 \"name\": \"Minecart with Chest\"\
4803 },\
4804 \"furnace_minecart\": {\
4805 \"id\": 343,\
4806 \"name\": \"Minecart with Furnace\"\
4807 },\
4808 \"egg\": {\
4809 \"id\": 344,\
4810 \"name\": \"Egg\"\
4811 },\
4812 \"compass\": {\
4813 \"id\": 345,\
4814 \"name\": \"Compass\"\
4815 },\
4816 \"fishing_rod\": {\
4817 \"id\": 346,\
4818 \"name\": \"Fishing Rod\"\
4819 },\
4820 \"clock\": {\
4821 \"id\": 347,\
4822 \"name\": \"Clock\"\
4823 },\
4824 \"glowstone_dust\": {\
4825 \"id\": 348,\
4826 \"name\": \"Glowstone Dust\"\
4827 },\
4828 \"fish\": {\
4829 \"id\": 349,\
4830 \"name\": [\"Raw Fish\",\
4831 \"Raw Salmon\",\
4832 \"Clownfish\",\
4833 \"Pufferfish\"]\
4834 },\
4835 \"cooked_fish\": {\
4836 \"id\": 350,\
4837 \"name\": [\"Cooked Fish\",\
4838 \"Cooked Salmon\"]\
4839 },\
4840 \"dye\": {\
4841 \"id\": 351,\
4842 \"name\": [\"Ink Sack\",\
4843 \"Rose Red\",\
4844 \"Cactus Green\",\
4845 \"Cocoa Bean\",\
4846 \"Lapis Lazuli\",\
4847 \"Purple Dye\",\
4848 \"Cyan Dye\",\
4849 \"Light Gray Dye\",\
4850 \"Gray Dye\",\
4851 \"Pink Dye\",\
4852 \"Lime Dye\",\
4853 \"Dandelion Yellow\",\
4854 \"Light Blue Dye\",\
4855 \"Magenta Dye\",\
4856 \"Orange Dye\",\
4857 \"Bone Meal\"]\
4858 },\
4859 \"bone\": {\
4860 \"id\": 352,\
4861 \"name\": \"Bone\"\
4862 },\
4863 \"sugar\": {\
4864 \"id\": 353,\
4865 \"name\": \"Sugar\"\
4866 },\
4867 \"bed-block\": {\
4868 \"id\": 355,\
4869 \"name\": \"Bed\",\
4870 \"place\": \"bed\",\
4871 },\
4872 \"repeater\": {\
4873 \"id\": 356,\
4874 \"name\": \"Redstone Repeater\",\
4875 \"place\": \"repeater\",\
4876 },\
4877 \"cookie\": {\
4878 \"id\": 357,\
4879 \"name\": \"Cookie\"\
4880 },\
4881 \"filled_map\": {\
4882 \"id\": 358,\
4883 \"name\": \"Map\"\
4884 },\
4885 \"shears\": {\
4886 \"id\": 359,\
4887 \"name\": \"Shears\"\
4888 },\
4889 \"melon\": {\
4890 \"id\": 360,\
4891 \"name\": \"Melon\"\
4892 },\
4893 \"pumpkin_seeds\": {\
4894 \"id\": 361,\
4895 \"name\": \"Pumpkin Seeds\"\
4896 },\
4897 \"melon_seeds\": {\
4898 \"id\": 362,\
4899 \"name\": \"Melon Seeds\"\
4900 },\
4901 \"beef\": {\
4902 \"id\": 363,\
4903 \"name\": \"Raw Beef\"\
4904 },\
4905 \"cooked_beef\": {\
4906 \"id\": 364,\
4907 \"name\": \"Steak\"\
4908 },\
4909 \"chicken\": {\
4910 \"id\": 365,\
4911 \"name\": \"Raw Chicken\"\
4912 },\
4913 \"cooked_chicken\": {\
4914 \"id\": 366,\
4915 \"name\": \"Cooked Chicken\"\
4916 },\
4917 \"rotten_flesh\": {\
4918 \"id\": 367,\
4919 \"name\": \"Rotten Flesh\"\
4920 },\
4921 \"ender_pearl\": {\
4922 \"id\": 368,\
4923 \"name\": \"Ender Pearl\"\
4924 },\
4925 \"blaze_rod\": {\
4926 \"id\": 369,\
4927 \"name\": \"Blaze Rod\"\
4928 },\
4929 \"ghast_tear\": {\
4930 \"id\": 370,\
4931 \"name\": \"Ghast Tear\"\
4932 },\
4933 \"gold_nugget\": {\
4934 \"id\": 371,\
4935 \"name\": \"Gold Nugget\"\
4936 },\
4937 \"potion\": {\
4938 \"id\": 373,\
4939 \"name\": \"Potion\"\
4940 },\
4941 \"glass_bottle\": {\
4942 \"id\": 374,\
4943 \"name\": \"Glass Bottle\"\
4944 },\
4945 \"spider_eye\": {\
4946 \"id\": 375,\
4947 \"name\": \"Spider Eye\"\
4948 },\
4949 \"fermented_spider_eye\": {\
4950 \"id\": 376,\
4951 \"name\": \"Fermented Spider Eye\"\
4952 },\
4953 \"blaze_powder\": {\
4954 \"id\": 377,\
4955 \"name\": \"Blaze Powder\"\
4956 },\
4957 \"magma_cream\": {\
4958 \"id\": 378,\
4959 \"name\": \"Magma Cream\"\
4960 },\
4961 \"ender_eye\": {\
4962 \"id\": 381,\
4963 \"name\": \"Eye of Ender\"\
4964 },\
4965 \"speckled_melon\": {\
4966 \"id\": 382,\
4967 \"name\": \"Glistering Melon\"\
4968 },\
4969 \"spawn_egg\": {\
4970 \"id\": 383,\
4971 \"name\": \"Spawn Egg\"\
4972 },\
4973 \"experience_bottle\": {\
4974 \"id\": 384,\
4975 \"name\": \"Bottle o' Enchanting\"\
4976 },\
4977 \"fire_charge\": {\
4978 \"id\": 385,\
4979 \"name\": \"Fire Charge\"\
4980 },\
4981 \"writable_book\": {\
4982 \"id\": 386,\
4983 \"name\": \"Book and Quill\"\
4984 },\
4985 \"written_book\": {\
4986 \"id\": 387,\
4987 \"name\": \"Written Book\"\
4988 },\
4989 \"emerald\": {\
4990 \"id\": 388,\
4991 \"name\": \"Emerald\"\
4992 },\
4993 \"item_frame\": {\
4994 \"id\": 389,\
4995 \"name\": \"Item Frame\"\
4996 },\
4997 \"carrot\": {\
4998 \"id\": 391,\
4999 \"name\": \"Carrot\",\
5000 },\
5001 \"potato\": {\
5002 \"id\": 392,\
5003 \"name\": \"Potato\",\
5004 },\
5005 \"baked_potato\": {\
5006 \"id\": 393,\
5007 \"name\": \"Baked Potato\"\
5008 },\
5009 \"poisonous_potato\": {\
5010 \"id\": 394,\
5011 \"name\": \"Poisonous Potato\"\
5012 },\
5013 \"map\": {\
5014 \"id\": 395,\
5015 \"name\": \"Empty Map\"\
5016 },\
5017 \"golden_carrot\": {\
5018 \"id\": 396,\
5019 \"name\": \"Golden Carrot\"\
5020 },\
5021 \"carrot_on_a_stick\": {\
5022 \"id\": 398,\
5023 \"name\": \"Carrot on a Stick\"\
5024 },\
5025 \"nether_star\": {\
5026 \"id\": 399,\
5027 \"name\": \"Nether Star\"\
5028 },\
5029 \"pumpkin_pie\": {\
5030 \"id\": 400,\
5031 \"name\": \"Pumpkin Pie\"\
5032 },\
5033 \"fireworks\": {\
5034 \"id\": 401,\
5035 \"name\": \"Firework Rocket\"\
5036 },\
5037 \"firework_charge\": {\
5038 \"id\": 402,\
5039 \"name\": \"Firework Star\"\
5040 },\
5041 \"enchanted_book\": {\
5042 \"id\": 403,\
5043 \"name\": \"Enchanted Book\"\
5044 },\
5045 \"comparator\": {\
5046 \"id\": 404,\
5047 \"name\": \"Redstone Comparator\",\
5048 \"place\": \"comparator\",\
5049 },\
5050 \"netherbrick\": {\
5051 \"id\": 405,\
5052 \"name\": \"Nether Brick\"\
5053 },\
5054 \"quartz\": {\
5055 \"id\": 406,\
5056 \"name\": \"Nether Quartz\"\
5057 },\
5058 \"tnt_minecart\": {\
5059 \"id\": 407,\
5060 \"name\": \"Minecart with TNT\"\
5061 },\
5062 \"hopper_minecart\": {\
5063 \"id\": 408,\
5064 \"name\": \"Minecart with Hopper\"\
5065 },\
5066 \"prismarine_shard\": {\
5067 \"id\": 409,\
5068 \"name\": \"Prismarine Shard\"\
5069 },\
5070 \"prismarine_crystals\": {\
5071 \"id\": 410,\
5072 \"name\": \"Prismarine Crystals\"\
5073 },\
5074 \"rabbit\": {\
5075 \"id\": 411,\
5076 \"name\": \"Raw Rabbit\"\
5077 },\
5078 \"cooked_rabbit\": {\
5079 \"id\": 412,\
5080 \"name\": \"Cooked Rabbit\"\
5081 },\
5082 \"rabbit_stew\": {\
5083 \"id\": 413,\
5084 \"name\": \"Rabbit Stew\"\
5085 },\
5086 \"rabbit_foot\": {\
5087 \"id\": 414,\
5088 \"name\": \"Rabbit's Foot\"\
5089 },\
5090 \"rabbit_hide\": {\
5091 \"id\": 415,\
5092 \"name\": \"Rabbit Hide\"\
5093 },\
5094 \"armor_stand\": {\
5095 \"id\": 416,\
5096 \"name\": \"Armor Stand\"\
5097 },\
5098 \"iron_horse_armor\": {\
5099 \"id\": 417,\
5100 \"name\": \"Iron Horse Armor\"\
5101 },\
5102 \"golden_horse_armor\": {\
5103 \"id\": 418,\
5104 \"name\": \"Golden Horse Armor\"\
5105 },\
5106 \"diamond_horse_armor\": {\
5107 \"id\": 419,\
5108 \"name\": \"Diamond Horse Armor\"\
5109 },\
5110 \"lead\": {\
5111 \"id\": 420,\
5112 \"name\": \"Lead\"\
5113 },\
5114 \"name_tag\": {\
5115 \"id\": 421,\
5116 \"name\": \"Name Tag\"\
5117 },\
5118 \"command_block_minecart\": {\
5119 \"id\": 422,\
5120 \"name\": \"Minecart with Command Block\"\
5121 },\
5122 \"mutton\": {\
5123 \"id\": 423,\
5124 \"name\": \"Raw Mutton\"\
5125 },\
5126 \"cooked_mutton\": {\
5127 \"id\": 424,\
5128 \"name\": \"Cooked Mutton\"\
5129 },\
5130 \"banner\": {\
5131 \"id\": 425,\
5132 \"name\": \"Banner\",\
5133 },\
5134 \"chorus_fruit\": {\
5135 \"id\": 432,\
5136 \"name\": \"Chorus Fruit\"\
5137 },\
5138 \"popped_chorus_fruit\": {\
5139 \"id\": 433,\
5140 \"name\": \"Popped Chorus Fruit\"\
5141 },\
5142 \"beetroot\": {\
5143 \"id\": 434,\
5144 \"name\": \"Beetroot\"\
5145 },\
5146 \"beetroot_seeds\": {\
5147 \"id\": 435,\
5148 \"name\": \"Beetroot Seeds\"\
5149 },\
5150 \"beetroot_soup\": {\
5151 \"id\": 436,\
5152 \"name\": \"Beetroot Soup\"\
5153 },\
5154 \"dragon_breath\": {\
5155 \"id\": 437,\
5156 \"name\": \"Dragon's Breath\"\
5157 },\
5158 \"splash_potion\": {\
5159 \"id\": 438,\
5160 \"name\": \"Splash Potion\"\
5161 },\
5162 \"spectral_arrow\": {\
5163 \"id\": 439,\
5164 \"name\": \"Spectral Arrow\"\
5165 },\
5166 \"tipped_arrow\": {\
5167 \"id\": 440,\
5168 \"name\": \"Tipped Arrow\"\
5169 },\
5170 \"lingering_potion\": {\
5171 \"id\": 441,\
5172 \"name\": \"Lingering Potion\"\
5173 },\
5174 \"shield\": {\
5175 \"id\": 442,\
5176 \"name\": \"Shield\"\
5177 },\
5178 \"elytra\": {\
5179 \"id\": 443,\
5180 \"name\": \"Elytra\"\
5181 },\
5182 \"spruce_boat\": {\
5183 \"id\": 444,\
5184 \"name\": \"Spruce Boat\"\
5185 },\
5186 \"birch_boat\": {\
5187 \"id\": 445,\
5188 \"name\": \"Birch Boat\"\
5189 },\
5190 \"jungle_boat\": {\
5191 \"id\": 446,\
5192 \"name\": \"Jungle Boat\"\
5193 },\
5194 \"acacia_boat\": {\
5195 \"id\": 447,\
5196 \"name\": \"Acacia Boat\"\
5197 },\
5198 \"dark_oak_boat\": {\
5199 \"id\": 448,\
5200 \"name\": \"Dark Oak Boat\"\
5201 },\
5202 \"totem_of_undying\": {\
5203 \"id\": 449,\
5204 \"name\": \"Totem of Undying\"\
5205 },\
5206 \"shulker_shell\": {\
5207 \"id\": 450,\
5208 \"name\": \"Shulker Shell\"\
5209 },\
5210 \"iron_nugget\": {\
5211 \"id\": 452,\
5212 \"name\": \"Iron Nugget\"\
5213 },\
5214 \"record_13\": {\
5215 \"id\": 2256,\
5216 \"name\": \"13 Disc\"\
5217 },\
5218 \"record_cat\": {\
5219 \"id\": 2257,\
5220 \"name\": \"Cat Disc\"\
5221 }\
5222 \"record_blocks\": {\
5223 \"id\": 2258,\
5224 \"name\": \"Blocks Disc\"\
5225 },\
5226 \"record_chirp\": {\
5227 \"id\": 2259,\
5228 \"name\": \"Chirp Disc\"\
5229 },\
5230 \"record_far\": {\
5231 \"id\": 2260,\
5232 \"name\": \"Far Disc\"\
5233 },\
5234 \"record_mall\": {\
5235 \"id\": 2261,\
5236 \"name\": \"Mall Disc\"\
5237 },\
5238 \"record_mellohi\": {\
5239 \"id\": 2262,\
5240 \"name\": \"Mellohi Disc\"\
5241 },\
5242 \"record_stal\": {\
5243 \"id\": 2263,\
5244 \"name\": \"Stal Disc\"\
5245 },\
5246 \"record_strad\": {\
5247 \"id\": 2264,\
5248 \"name\": \"Strad Disc\"\
5249 },\
5250 \"record_ward\": {\
5251 \"id\": 2265,\
5252 \"name\": \"Ward Disc\"\
5253 },\
5254 \"record_11\": {\
5255 \"id\": 2266,\
5256 \"name\": \"11 Disc\"\
5257 },\
5258 \"record_wait\": {\
5259 \"id\": 2267,\
5260 \"name\": \"Wait Disc\"\
5261 },\
5262}",
5263 [ "core/apis/chestAdapter.lua" ] = "local class = require('opus.class')\
5264local itemDB = require('core.itemDB')\
5265local Peripheral = require('opus.peripheral')\
5266local Util = require('opus.util')\
5267\
5268local os = _G.os\
5269\
5270local ChestAdapter = class()\
5271\
5272local convertNames = {\
5273 name = 'id',\
5274 damage = 'dmg',\
5275 maxCount = 'max_size',\
5276 count = 'qty',\
5277 displayName = 'display_name',\
5278 maxDamage = 'max_dmg',\
5279 nbtHash = 'nbt_hash',\
5280}\
5281\
5282-- Strip off color prefix\
5283local function safeString(text)\
5284\
5285 local val = text:byte(1)\
5286\
5287 if val < 32 or val > 128 then\
5288\
5289 local newText = {}\
5290 for i = 4, #text do\
5291 val = text:byte(i)\
5292 newText[i - 3] = (val > 31 and val < 127) and val or 63\
5293 end\
5294 return string.char(unpack(newText))\
5295 end\
5296\
5297 return text\
5298end\
5299\
5300local function convertItem(item)\
5301 for k,v in pairs(convertNames) do\
5302 item[k] = item[v]\
5303 item[v] = nil\
5304 end\
5305 item.displayName = safeString(item.displayName)\
5306end\
5307\
5308function ChestAdapter:init(args)\
5309 local defaults = {\
5310 name = 'chest',\
5311 }\
5312 Util.merge(self, defaults)\
5313 Util.merge(self, args)\
5314\
5315 local chest\
5316 if not self.side then\
5317 chest = Peripheral.getByMethod('getAllStacks')\
5318 else\
5319 chest = Peripheral.getBySide(self.side)\
5320 if chest and not chest.getAllStacks then\
5321 chest = nil\
5322 end\
5323 end\
5324\
5325 if chest then\
5326 Util.merge(self, chest)\
5327\
5328 if chest.listAvailableItems then\
5329 self.list = chest.listAvailableItems\
5330 end\
5331 end\
5332end\
5333\
5334function ChestAdapter:isValid()\
5335 return not not self.getAllStacks\
5336end\
5337\
5338function ChestAdapter:refresh(throttle)\
5339 return self:listItems(throttle)\
5340end\
5341\
5342-- provide a consolidated list of items\
5343function ChestAdapter:listItems(throttle)\
5344 local cache = { }\
5345 local items = { }\
5346 throttle = throttle or Util.throttle()\
5347\
5348 -- getAllStacks sometimes fails\
5349 local s, m = pcall(function()\
5350 for _,v in pairs(self.getAllStacks(false)) do\
5351 if v.qty > 0 then\
5352 convertItem(v)\
5353 local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')\
5354\
5355 local entry = cache[key]\
5356 if not entry then\
5357 entry = itemDB:get(v) or itemDB:add(v)\
5358 entry = Util.shallowCopy(entry)\
5359 entry.count = 0\
5360 cache[key] = entry\
5361 table.insert(items, entry)\
5362 end\
5363 entry.count = entry.count + v.count\
5364 throttle()\
5365 end\
5366 itemDB:flush()\
5367 end\
5368 end)\
5369 if s then\
5370 if not Util.empty(items) then\
5371 self.cache = cache\
5372 return items\
5373 end\
5374 else\
5375 _G._syslog(m)\
5376 end\
5377end\
5378\
5379function ChestAdapter:getItemInfo(item)\
5380 if not self.cache then\
5381 self:listItems()\
5382 end\
5383 local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')\
5384 return self.cache[key]\
5385end\
5386\
5387function ChestAdapter:provide(item, qty, slot, direction)\
5388 pcall(function()\
5389 for key,stack in Util.rpairs(self.getAllStacks(false)) do\
5390 if stack.id == item.name and\
5391 (not item.damage or stack.dmg == item.damage) and\
5392 (not item.nbtHash or stack.nbt_hash == item.nbtHash) then\
5393 local amount = math.min(qty, stack.qty)\
5394 if amount > 0 then\
5395 self.pushItemIntoSlot(direction or self.direction, key, amount, slot)\
5396 end\
5397 qty = qty - amount\
5398 if qty <= 0 then\
5399 break\
5400 end\
5401 end\
5402 end\
5403 end)\
5404end\
5405\
5406function ChestAdapter:extract(slot, qty, toSlot)\
5407 if toSlot then\
5408 self.pushItemIntoSlot(self.direction, slot, qty, toSlot)\
5409 else\
5410 self.pushItem(self.direction, slot, qty)\
5411 end\
5412end\
5413\
5414function ChestAdapter:insert(slot, qty, toSlot)\
5415 -- toSlot not tested ...\
5416 local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)\
5417 if not s and m then\
5418 os.sleep(1)\
5419 pcall(self.pullItem, self.direction, slot, qty, toSlot)\
5420 end\
5421end\
5422\
5423return ChestAdapter",
5424 [ "monitor/.package" ] = "{\
5425 title = 'Various monitor related programs',\
5426 repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/monitor',\
5427 description = [[Mirror terminal to monitor, Monitor Window Manager (mwm), and more]],\
5428 license = 'MIT',\
5429}",
5430 [ "common/etc/fstab" ] = "packages/common/ascii.lua urlfs http://pastebin.com/raw/u3kcnyjd\
5431packages/common/hexedit.lua urlfs https://pastebin.com/raw/Ds9ajsp4\
5432packages/common/colors.lua urlfs https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/ignore/colors.lua\
5433packages/common/cowsay.lua urlfs https://pastebin.com/raw/n00VQJsw\
5434packages/common/calc.lua urlfs https://pastebin.com/raw/nAinUn1h\
5435packages/common/write.lua urlfs https://pastebin.com/raw/RSyhCjqv\
5436packages/common/apis/debugger.lua urlfs https://raw.githubusercontent.com/slembcke/debugger.lua/master/debugger.lua",
5437 [ "monitor/etc/apps.db" ] = "{\
5438 [ \"58ec8d6e36e346d9f42eb43935652e3e58e2c829\" ] = {\
5439 title = \"Mwm\",\
5440 category = \"Apps\",\
5441 icon = \"\\030f\\031f \\0304 \\010\\030f\\031dshell]\\0304\\0314 \\010\\0304\\031f \",\
5442 iconExt = \"\\030 \\031f\\0305\\031f\\155\\030f\\128\\031d\\152\\140\\030d\\031f\\151\\030f\\128\\128\\0304\\0314\\128\\010\\030 \\031f\\030f\\0315\\152\\129\\030d\\031f\\141\\030f\\031d\\153\\030d\\031f\\149\\030f\\031d\\131\\148\\0304\\0314\\128\\010\\030 \\031f\\0304\\031f\\131\\131\\131\\131\\131\\131\\131\\030e\\0314\\131\",\
5443 run = \"mwm.lua usr/config/mwm\",\
5444 },\
5445}",
5446 [ "monitor/help/mirror.txt" ] = "Mirror the terminal or a program on a monitor. Monitor touch events are translated to mouse click events.\
5447\
5448Optional arguments:\
5449 -s : set monitor to .5 scaling\
5450 -r : resize the terminal to the size of the monitor\
5451 -e : run a program on the monitor\
5452\
5453If the -e argument is not used, the current terminal will be mirrored.\
5454\
5455Example:\
5456mirror -s -r -e edit /startup",
5457 [ "minify/minifyDir.lua" ] = "local fs = _G.fs\
5458local shell = _ENV.shell\
5459\
5460local function recurse(path)\
5461 if fs.isDir(path) then\
5462 for _, v in pairs(fs.listEx(path)) do\
5463 if not v.isReadOnly then\
5464 recurse(fs.combine(path, v.name))\
5465 end\
5466 end\
5467 elseif path:match('%.lua$') and not fs.isReadOnly(path) then\
5468 local sz = fs.getSize(path)\
5469 shell.run('minify.lua minify ' .. path)\
5470 print(string.format('%s : %.2f%%', path, (sz - fs.getSize(path)) / sz * 100))\
5471 end\
5472end\
5473\
5474local path = ({ ... })[1] or error('Syntax: minifyDir PATH')\
5475\
5476path = fs.combine(path, '')\
5477if not fs.isDir(path) then\
5478 error('Invalid path')\
5479end\
5480\
5481recurse(path)",
5482 [ "common/Events.lua" ] = "local Event = require('opus.event')\
5483local UI = require('opus.ui')\
5484local Util = require('opus.util')\
5485\
5486local multishell = _ENV.multishell\
5487local kernel = _G.kernel\
5488\
5489UI:configure('Events', ...)\
5490\
5491local page = UI.Page {\
5492 menuBar = UI.MenuBar {\
5493 buttons = {\
5494 { text = 'Filter', event = 'filter' },\
5495 { text = 'Reset', event = 'reset' },\
5496 { text = 'Pause ', event = 'toggle', name = 'pauseButton' },\
5497 },\
5498 },\
5499 grid = UI.ScrollingGrid {\
5500 y = 2,\
5501 columns = {\
5502 { key = 'event' },\
5503 { key = 'p1' },\
5504 { key = 'p2' },\
5505 { key = 'p3' },\
5506 { key = 'p4' },\
5507 { key = 'p5' },\
5508 },\
5509 autospace = true,\
5510 disableHeader = true,\
5511 getDisplayValues = function(_, row)\
5512 row = Util.shallowCopy(row)\
5513\
5514 local function tovalue(s)\
5515 if type(s) == 'table' then\
5516 return 'table'\
5517 end\
5518 return s\
5519 end\
5520\
5521 for k,v in pairs(row) do\
5522 row[k] = tovalue(v)\
5523 end\
5524\
5525 return row\
5526 end,\
5527 },\
5528 accelerators = {\
5529 f = 'filter',\
5530 p = 'toggle',\
5531 r = 'reset',\
5532 c = 'clear',\
5533 [ 'control-q' ] = 'quit',\
5534 },\
5535 filtered = { },\
5536 eventHandler = function(self, event)\
5537 if event.type == 'filter' then\
5538 local entry = self.grid:getSelected()\
5539 self.filtered[entry.event] = true\
5540\
5541 elseif event.type == 'toggle' then\
5542 self.paused = not self.paused\
5543 if self.paused then\
5544 self.menuBar.pauseButton.text = 'Resume'\
5545 else\
5546 self.menuBar.pauseButton.text = 'Pause '\
5547 end\
5548 self.menuBar:draw()\
5549\
5550 elseif event.type == 'grid_select' then\
5551 multishell.openTab(_ENV, {\
5552 path = 'sys/apps/Lua.lua',\
5553 args = { event.selected },\
5554 focused = true,\
5555 })\
5556\
5557 elseif event.type == 'reset' then\
5558 self.filtered = { }\
5559 self.grid:setValues({ })\
5560 self.grid:draw()\
5561 if self.paused then\
5562 self:emit({ type = 'toggle' })\
5563 end\
5564\
5565 elseif event.type == 'clear' then\
5566 self.grid:setValues({ })\
5567 self.grid:draw()\
5568\
5569 elseif event.type == 'quit' then\
5570 UI:quit()\
5571\
5572 else\
5573 return UI.Page.eventHandler(self, event)\
5574 end\
5575 return true\
5576 end,\
5577}\
5578\
5579local updated = false\
5580local timerId = os.startTimer(1)\
5581\
5582Event.addRoutine(function()\
5583 while true do\
5584 local _, id = os.pullEvent('timer')\
5585 if id == timerId then\
5586 if updated then\
5587 while #page.grid.values > 100 do -- page.grid.height do\
5588 table.remove(page.grid.values, 100) -- #page.grid.values)\
5589 end\
5590 updated = false\
5591 page.grid:update()\
5592 page.grid:draw()\
5593 page:sync()\
5594 end\
5595 timerId = os.startTimer(1)\
5596 end\
5597 end\
5598end)\
5599\
5600local hookFunction = function(event, e)\
5601 if not page.filtered[event] and not page.paused and not (event == 'timer' and e[1] == timerId) then\
5602 updated = true\
5603 table.insert(page.grid.values, 1, {\
5604 event = event,\
5605 p1 = e[1],\
5606 p2 = e[2],\
5607 p3 = e[3],\
5608 p4 = e[4],\
5609 p5 = e[5],\
5610 })\
5611 end\
5612end\
5613\
5614kernel.hook('*', hookFunction)\
5615\
5616UI:setPage(page)\
5617UI:start()\
5618\
5619kernel.unhook('*', hookFunction)",
5620 [ "core/apis/chestAdapter18.lua" ] = "local class = require('opus.class')\
5621local Util = require('opus.util')\
5622local itemDB = require('core.itemDB')\
5623local Peripheral = require('opus.peripheral')\
5624\
5625local ChestAdapter = class()\
5626\
5627function ChestAdapter:init(args)\
5628 local defaults = {\
5629 name = 'chest',\
5630 adapter = 'ChestAdapter18'\
5631 }\
5632 Util.merge(self, defaults)\
5633 Util.merge(self, args)\
5634\
5635 local chest\
5636 if not self.side then\
5637 chest = Peripheral.getByMethod('list') or\
5638 Peripheral.getByMethod('listAvailableItems')\
5639 else\
5640 chest = Peripheral.getBySide(self.side)\
5641 if chest and not chest.list and not chest.listAvailableItems then\
5642 chest = nil\
5643 end\
5644 end\
5645\
5646 if chest then\
5647 Util.merge(self, chest)\
5648\
5649 if chest.listAvailableItems then\
5650 self.list = chest.listAvailableItems\
5651 end\
5652 end\
5653end\
5654\
5655function ChestAdapter:isValid()\
5656 return not not self.list\
5657end\
5658\
5659-- handle both AE/RS and generic inventory\
5660function ChestAdapter:getItemDetails(index, item)\
5661 if self.getItemMeta then\
5662 local s, detail = pcall(self.getItemMeta, index)\
5663 if not s or not detail or detail.name ~= item.name then\
5664 return\
5665 end\
5666 return detail\
5667 else\
5668 local detail = self.findItems(item)\
5669 if detail and #detail > 0 then\
5670 return detail[1].getMetadata()\
5671 end\
5672 end\
5673end\
5674\
5675function ChestAdapter:getCachedItemDetails(item, k)\
5676 local cached = itemDB:get(item)\
5677 if cached then\
5678 return cached\
5679 end\
5680\
5681 local detail = self:getItemDetails(k, item)\
5682 if detail then\
5683 return itemDB:add(detail)\
5684 end\
5685end\
5686\
5687function ChestAdapter:refresh(throttle)\
5688 return self:listItems(throttle)\
5689end\
5690\
5691-- provide a consolidated list of items\
5692function ChestAdapter:listItems(throttle)\
5693 for _ = 1, 5 do\
5694 local list = self:listItemsInternal(throttle)\
5695 if list then\
5696 return list\
5697 end\
5698 end\
5699 error('Error accessing inventory: ' .. self.direction)\
5700end\
5701\
5702function ChestAdapter:listItemsInternal(throttle)\
5703 local cache = { }\
5704 local items = { }\
5705 throttle = throttle or Util.throttle()\
5706\
5707 for k,v in pairs(self.list()) do\
5708 if v.count > 0 then\
5709 local key = table.concat({ v.name, v.damage, v.nbtHash }, ':')\
5710\
5711 local entry = cache[key]\
5712 if not entry then\
5713 entry = self:getCachedItemDetails(v, k)\
5714 if not entry then\
5715 return -- Inventory has changed\
5716 end\
5717 entry = Util.shallowCopy(entry)\
5718 entry.count = 0\
5719 cache[key] = entry\
5720 table.insert(items, entry)\
5721 end\
5722\
5723 if entry then\
5724 entry.count = entry.count + v.count\
5725 end\
5726 throttle()\
5727 end\
5728 end\
5729 itemDB:flush()\
5730\
5731 self.cache = cache\
5732 return items\
5733end\
5734\
5735function ChestAdapter:getItemInfo(item)\
5736 if not self.cache then\
5737 self:listItems()\
5738 end\
5739 local key = table.concat({ item.name, item.damage, item.nbtHash }, ':')\
5740 local items = self.cache or { }\
5741 return items[key]\
5742end\
5743\
5744function ChestAdapter:getPercentUsed()\
5745 if self.cache and self.getDrawerCount then\
5746 return math.floor(Util.size(self.cache) / self.getDrawerCount() * 100)\
5747 end\
5748 return 0\
5749end\
5750\
5751function ChestAdapter:provide(item, qty, slot, direction)\
5752 local total = 0\
5753\
5754 local _, m = pcall(function()\
5755 local stacks = self.list()\
5756 for key,stack in Util.rpairs(stacks) do\
5757 if stack.name == item.name and\
5758 stack.damage == item.damage and\
5759 stack.nbtHash == item.nbtHash then\
5760 local amount = math.min(qty, stack.count)\
5761 if amount > 0 then\
5762 amount = self.pushItems(direction or self.direction, key, amount, slot)\
5763 end\
5764 qty = qty - amount\
5765 total = total + amount\
5766 if qty <= 0 then\
5767 break\
5768 end\
5769 end\
5770 end\
5771 end)\
5772 return total, m\
5773end\
5774\
5775function ChestAdapter:extract(slot, qty, toSlot, direction)\
5776 return self.pushItems(direction or self.direction, slot, qty, toSlot)\
5777end\
5778\
5779function ChestAdapter:insert(slot, qty, toSlot, direction)\
5780 return self.pullItems(direction or self.direction, slot, qty, toSlot)\
5781end\
5782\
5783return ChestAdapter",
5784 [ "minify/minify.lua" ] = "--[[\
5785MIT License\
5786\
5787Copyright (c) 2017 Mark Langen\
5788\
5789Permission is hereby granted, free of charge, to any person obtaining a copy\
5790of this software and associated documentation files (the \"Software\"), to deal\
5791in the Software without restriction, including without limitation the rights\
5792to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
5793copies of the Software, and to permit persons to whom the Software is\
5794furnished to do so, subject to the following conditions:\
5795\
5796The above copyright notice and this permission notice shall be included in all\
5797copies or substantial portions of the Software.\
5798\
5799THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
5800IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
5801FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
5802AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
5803LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
5804OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
5805SOFTWARE.\
5806]]\
5807\
5808local function lookupify(tb)\
5809 for _, v in pairs(tb) do\
5810 tb[v] = true\
5811 end\
5812 return tb\
5813end\
5814\
5815local function CountTable(tb)\
5816 local c = 0\
5817 for _ in pairs(tb) do c = c + 1 end\
5818 return c\
5819end\
5820\
5821local function FormatTableInt(tb, atIndent, ignoreFunc)\
5822 if tb.Print then\
5823 return tb.Print()\
5824 end\
5825 atIndent = atIndent or 0\
5826 local useNewlines = (CountTable(tb) > 1)\
5827 local baseIndent = string.rep(' ', atIndent+1)\
5828 local out = \"{\"..(useNewlines and '\\n' or '')\
5829 for k, v in pairs(tb) do\
5830 if type(v) ~= 'function' and not ignoreFunc(k) then\
5831 out = out..(useNewlines and baseIndent or '')\
5832 if type(k) == 'number' then\
5833 --nothing to do\
5834 elseif type(k) == 'string' and k:match(\"^[A-Za-z_][A-Za-z0-9_]*$\") then\
5835 out = out..k..\" = \"\
5836 elseif type(k) == 'string' then\
5837 out = out..\"[\\\"\"..k..\"\\\"] = \"\
5838 else\
5839 out = out..\"[\"..tostring(k)..\"] = \"\
5840 end\
5841 if type(v) == 'string' then\
5842 out = out..\"\\\"\"..v..\"\\\"\"\
5843 elseif type(v) == 'number' then\
5844 out = out..v\
5845 elseif type(v) == 'table' then\
5846 out = out..FormatTableInt(v, atIndent+(useNewlines and 1 or 0), ignoreFunc)\
5847 else\
5848 out = out..tostring(v)\
5849 end\
5850 if next(tb, k) then\
5851 out = out..\",\"\
5852 end\
5853 if useNewlines then\
5854 out = out..'\\n'\
5855 end\
5856 end\
5857 end\
5858 out = out..(useNewlines and string.rep(' ', atIndent) or '')..\"}\"\
5859 return out\
5860end\
5861\
5862local function FormatTable(tb, ignoreFunc)\
5863 ignoreFunc = ignoreFunc or function()\
5864 return false\
5865 end\
5866 return FormatTableInt(tb, 0, ignoreFunc)\
5867end\
5868\
5869local WhiteChars = lookupify{' ', '\\n', '\\t', '\\r'}\
5870\
5871local CharacterForEscape = {['r'] = '\\r', ['n'] = '\\n', ['t'] = '\\t', ['\"'] = '\"', [\"'\"] = \"'\", ['\\\\'] = '\\\\'}\
5872\
5873local AllIdentStartChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',\
5874 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',\
5875 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',\
5876 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',\
5877 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',\
5878 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_'}\
5879\
5880local AllIdentChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',\
5881 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',\
5882 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',\
5883 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',\
5884 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',\
5885 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_',\
5886 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}\
5887\
5888local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}\
5889\
5890local HexDigits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\
5891 'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'}\
5892\
5893local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#', '.', ':'}\
5894\
5895local EqualSymbols = lookupify{'~', '=', '>', '<'}\
5896\
5897local Keywords = lookupify{\
5898 'and', 'break', 'do', 'else', 'elseif',\
5899 'end', 'false', 'for', 'function', 'goto', 'if',\
5900 'in', 'local', 'nil', 'not', 'or', 'repeat',\
5901 'return', 'then', 'true', 'until', 'while',\
5902};\
5903\
5904local BlockFollowKeyword = lookupify{'else', 'elseif', 'until', 'end'}\
5905\
5906local UnopSet = lookupify{'-', 'not', '#'}\
5907\
5908local BinopSet = lookupify{\
5909 '+', '-', '*', '/', '%', '^', '#',\
5910 '..', '.', ':',\
5911 '>', '<', '<=', '>=', '~=', '==',\
5912 'and', 'or'\
5913}\
5914\
5915local BinaryPriority = {\
5916 ['+'] = {6, 6};\
5917 ['-'] = {6, 6};\
5918 ['*'] = {7, 7};\
5919 ['/'] = {7, 7};\
5920 ['%'] = {7, 7};\
5921 ['^'] = {10, 9};\
5922 ['..'] = {5, 4};\
5923 ['=='] = {3, 3};\
5924 ['~='] = {3, 3};\
5925 ['>'] = {3, 3};\
5926 ['<'] = {3, 3};\
5927 ['>='] = {3, 3};\
5928 ['<='] = {3, 3};\
5929 ['and'] = {2, 2};\
5930 ['or'] = {1, 1};\
5931};\
5932local UnaryPriority = 8\
5933\
5934-- Eof, Ident, Keyword, Number, String, Symbol\
5935\
5936local function CreateLuaTokenStream(text)\
5937 -- Tracking for the current position in the buffer, and\
5938 -- the current line / character we are on.\
5939 local p = 1\
5940 local length = #text\
5941\
5942 -- Output buffer for tokens\
5943 local tokenBuffer = {}\
5944\
5945 -- Get a character, or '' if at eof\
5946 local function look(n)\
5947 n = p + (n or 0)\
5948 if n <= length then\
5949 return text:sub(n, n)\
5950 else\
5951 return ''\
5952 end\
5953 end\
5954 local function get()\
5955 if p <= length then\
5956 local c = text:sub(p, p)\
5957 p = p + 1\
5958 return c\
5959 else\
5960 return ''\
5961 end\
5962 end\
5963\
5964 -- Error\
5965 local olderr = error\
5966 local function error(str)\
5967 local q = 1\
5968 local line = 1\
5969 local char = 1\
5970 while q <= p do\
5971 if text:sub(q, q) == '\\n' then\
5972 line = line + 1\
5973 char = 1\
5974 else\
5975 char = char + 1\
5976 end\
5977 q = q + 1\
5978 end\
5979 for _, token in pairs(tokenBuffer) do\
5980 --print(token.Type..\"<\"..token.Source..\">\")\
5981 end\
5982 olderr(\"file<\"..line..\":\"..char..\">: \"..str)\
5983 end\
5984\
5985 -- Consume a long data with equals count of `eqcount'\
5986 local function longdata(eqcount)\
5987 while true do\
5988 local c = get()\
5989 if c == '' then\
5990 error(\"Unfinished long string.\")\
5991 elseif c == ']' then\
5992 local done = true -- Until contested\
5993 for _ = 1, eqcount do\
5994 if look() == '=' then\
5995 p = p + 1\
5996 else\
5997 done = false\
5998 break\
5999 end\
6000 end\
6001 if done and get() == ']' then\
6002 return\
6003 end\
6004 end\
6005 end\
6006 end\
6007\
6008 -- Get the opening part for a long data `[` `=`* `[`\
6009 -- Precondition: The first `[` has been consumed\
6010 -- Return: nil or the equals count\
6011 local function getopen()\
6012 local startp = p\
6013 while look() == '=' do\
6014 p = p + 1\
6015 end\
6016 if look() == '[' then\
6017 p = p + 1\
6018 return p - startp - 1\
6019 else\
6020 p = startp\
6021 return nil\
6022 end\
6023 end\
6024\
6025 -- Add token\
6026 local whiteStart = 1\
6027 local tokenStart = 1\
6028 local function token(type)\
6029 local tk = {\
6030 Type = type;\
6031 LeadingWhite = text:sub(whiteStart, tokenStart-1);\
6032 Source = text:sub(tokenStart, p-1);\
6033 }\
6034 table.insert(tokenBuffer, tk)\
6035 whiteStart = p\
6036 tokenStart = p\
6037 return tk\
6038 end\
6039\
6040 -- Parse tokens loop\
6041 while true do\
6042 -- Mark the whitespace start\
6043 whiteStart = p\
6044\
6045 -- Get the leading whitespace + comments\
6046 while true do\
6047 local c = look()\
6048 if c == '' then\
6049 break\
6050 elseif c == '-' then\
6051 if look(1) == '-' then\
6052 p = p + 2\
6053 -- Consume comment body\
6054 if look() == '[' then\
6055 p = p + 1\
6056 local eqcount = getopen()\
6057 if eqcount then\
6058 -- Long comment body\
6059 longdata(eqcount)\
6060 else\
6061 -- Normal comment body\
6062 while true do\
6063 local c2 = get()\
6064 if c2 == '' or c2 == '\\n' then\
6065 break\
6066 end\
6067 end\
6068 end\
6069 else\
6070 -- Normal comment body\
6071 while true do\
6072 local c2 = get()\
6073 if c2 == '' or c2 == '\\n' then\
6074 break\
6075 end\
6076 end\
6077 end\
6078 else\
6079 break\
6080 end\
6081 elseif WhiteChars[c] then\
6082 p = p + 1\
6083 else\
6084 break\
6085 end\
6086 end\
6087 -- local leadingWhite = text:sub(whiteStart, p-1)\
6088\
6089 -- Mark the token start\
6090 tokenStart = p\
6091\
6092 -- Switch on token type\
6093 local c1 = get()\
6094 if c1 == '' then\
6095 -- End of file\
6096 token('Eof')\
6097 break\
6098 elseif c1 == '\\'' or c1 == '\\\"' then\
6099 -- String constant\
6100 while true do\
6101 local c2 = get()\
6102 if c2 == '\\\\' then\
6103 local c3 = get()\
6104 local esc = CharacterForEscape[c3]\
6105 if not esc then\
6106 error(\"Invalid Escape Sequence `\"..c3..\"`.\")\
6107 end\
6108 elseif c2 == c1 then\
6109 break\
6110 end\
6111 end\
6112 token('String')\
6113 elseif AllIdentStartChars[c1] then\
6114 -- Ident or Keyword\
6115 while AllIdentChars[look()] do\
6116 p = p + 1\
6117 end\
6118 if Keywords[text:sub(tokenStart, p-1)] then\
6119 token('Keyword')\
6120 else\
6121 token('Ident')\
6122 end\
6123 elseif Digits[c1] or (c1 == '.' and Digits[look()]) then\
6124 -- Number\
6125 if c1 == '0' and look() == 'x' then\
6126 p = p + 1\
6127 -- Hex number\
6128 while HexDigits[look()] do\
6129 p = p + 1\
6130 end\
6131 else\
6132 -- Normal Number\
6133 while Digits[look()] do\
6134 p = p + 1\
6135 end\
6136 if look() == '.' then\
6137 -- With decimal point\
6138 p = p + 1\
6139 while Digits[look()] do\
6140 p = p + 1\
6141 end\
6142 end\
6143 if look() == 'e' or look() == 'E' then\
6144 -- With exponent\
6145 p = p + 1\
6146 if look() == '-' then\
6147 p = p + 1\
6148 end\
6149 while Digits[look()] do\
6150 p = p + 1\
6151 end\
6152 end\
6153 end\
6154 token('Number')\
6155 elseif c1 == '[' then\
6156 -- '[' Symbol or Long String\
6157 local eqCount = getopen()\
6158 if eqCount then\
6159 -- Long string\
6160 longdata(eqCount)\
6161 token('String')\
6162 else\
6163 -- Symbol\
6164 token('Symbol')\
6165 end\
6166 elseif c1 == '.' then\
6167 -- Greedily consume up to 3 `.` for . / .. / ... tokens\
6168 if look() == '.' then\
6169 get()\
6170 if look() == '.' then\
6171 get()\
6172 end\
6173 end\
6174 token('Symbol')\
6175 elseif EqualSymbols[c1] then\
6176 if look() == '=' then\
6177 p = p + 1\
6178 end\
6179 token('Symbol')\
6180 elseif Symbols[c1] then\
6181 token('Symbol')\
6182 else\
6183 error(\"Bad symbol `\"..c1..\"` in source.\")\
6184 end\
6185 end\
6186 return tokenBuffer\
6187end\
6188\
6189local function CreateLuaParser(text)\
6190 -- Token stream and pointer into it\
6191 local tokens = CreateLuaTokenStream(text)\
6192 -- for _, tok in pairs(tokens) do\
6193 -- print(tok.Type..\": \"..tok.Source)\
6194 -- end\
6195 local p = 1\
6196\
6197 local function get()\
6198 local tok = tokens[p]\
6199 if p < #tokens then\
6200 p = p + 1\
6201 end\
6202 return tok\
6203 end\
6204 local function peek(n)\
6205 n = p + (n or 0)\
6206 return tokens[n] or tokens[#tokens]\
6207 end\
6208\
6209 local function getTokenStartPosition(token)\
6210 local line = 1\
6211 local char = 0\
6212 local tkNum = 1\
6213 while true do\
6214 local tk = tokens[tkNum]\
6215 if tk == token then\
6216 text = tk.LeadingWhite\
6217 else\
6218 text = tk.LeadingWhite..tk.Source\
6219 end\
6220 for i = 1, #text do\
6221 local c = text:sub(i, i)\
6222 if c == '\\n' then\
6223 line = line + 1\
6224 char = 0\
6225 else\
6226 char = char + 1\
6227 end\
6228 end\
6229 if tk == token then\
6230 break\
6231 end\
6232 tkNum = tkNum + 1\
6233 end\
6234 return line..\":\"..(char+1)\
6235 end\
6236 local function debugMark()\
6237 local tk = peek()\
6238 return \"<\"..tk.Type..\" `\"..tk.Source..\"`> at: \"..getTokenStartPosition(tk)\
6239 end\
6240\
6241 local function isBlockFollow()\
6242 local tok = peek()\
6243 return tok.Type == 'Eof' or (tok.Type == 'Keyword' and BlockFollowKeyword[tok.Source])\
6244 end\
6245 local function isUnop()\
6246 return UnopSet[peek().Source] or false\
6247 end\
6248 local function isBinop()\
6249 return BinopSet[peek().Source] or false\
6250 end\
6251 local function expect(type, source)\
6252 local tk = peek()\
6253 if tk.Type == type and (source == nil or tk.Source == source) then\
6254 return get()\
6255 else\
6256 for i = -3, 3 do\
6257 --print(\"Tokens[\"..i..\"] = `\"..peek(i).Source..\"`\")\
6258 end\
6259 if source then\
6260 error(getTokenStartPosition(tk)..\": `\"..source..\"` expected.\")\
6261 else\
6262 error(getTokenStartPosition(tk)..\": \"..type..\" expected.\")\
6263 end\
6264 end\
6265 end\
6266\
6267 local function MkNode(node)\
6268 local getf = node.GetFirstToken\
6269 local getl = node.GetLastToken\
6270 function node:GetFirstToken()\
6271 local t = getf(self)\
6272 assert(t)\
6273 return t\
6274 end\
6275 function node:GetLastToken()\
6276 local t = getl(self)\
6277 assert(t)\
6278 return t\
6279 end\
6280 return node\
6281 end\
6282\
6283 -- Forward decls\
6284 local block;\
6285 local expr;\
6286\
6287 -- Expression list\
6288 local function exprlist()\
6289 local exprList = {}\
6290 local commaList = {}\
6291 table.insert(exprList, expr())\
6292 while peek().Source == ',' do\
6293 table.insert(commaList, get())\
6294 table.insert(exprList, expr())\
6295 end\
6296 return exprList, commaList\
6297 end\
6298\
6299 local function prefixexpr()\
6300 local tk = peek()\
6301 if tk.Source == '(' then\
6302 local oparenTk = get()\
6303 local inner = expr()\
6304 local cparenTk = expect('Symbol', ')')\
6305 return MkNode{\
6306 Type = 'ParenExpr';\
6307 Expression = inner;\
6308 Token_OpenParen = oparenTk;\
6309 Token_CloseParen = cparenTk;\
6310 GetFirstToken = function(self)\
6311 return self.Token_OpenParen\
6312 end;\
6313 GetLastToken = function(self)\
6314 return self.Token_CloseParen\
6315 end;\
6316 }\
6317 elseif tk.Type == 'Ident' then\
6318 return MkNode{\
6319 Type = 'VariableExpr';\
6320 Token = get();\
6321 GetFirstToken = function(self)\
6322 return self.Token\
6323 end;\
6324 GetLastToken = function(self)\
6325 return self.Token\
6326 end;\
6327 }\
6328 else\
6329 print(debugMark())\
6330 error(getTokenStartPosition(tk)..\": Unexpected symbol\")\
6331 end\
6332 end\
6333\
6334 local function tableexpr()\
6335 local obrace = expect('Symbol', '{')\
6336 local entries = {}\
6337 local separators = {}\
6338 while peek().Source ~= '}' do\
6339 if peek().Source == '[' then\
6340 -- Index\
6341 local obrac = get()\
6342 local index = expr()\
6343 local cbrac = expect('Symbol', ']')\
6344 local eq = expect('Symbol', '=')\
6345 local value = expr()\
6346 table.insert(entries, {\
6347 EntryType = 'Index';\
6348 Index = index;\
6349 Value = value;\
6350 Token_OpenBracket = obrac;\
6351 Token_CloseBracket = cbrac;\
6352 Token_Equals = eq;\
6353 })\
6354 elseif peek().Type == 'Ident' and peek(1).Source == '=' then\
6355 -- Field\
6356 local field = get()\
6357 local eq = get()\
6358 local value = expr()\
6359 table.insert(entries, {\
6360 EntryType = 'Field';\
6361 Field = field;\
6362 Value = value;\
6363 Token_Equals = eq;\
6364 })\
6365 else\
6366 -- Value\
6367 local value = expr()\
6368 table.insert(entries, {\
6369 EntryType = 'Value';\
6370 Value = value;\
6371 })\
6372 end\
6373\
6374 -- Comma or Semicolon separator\
6375 if peek().Source == ',' or peek().Source == ';' then\
6376 table.insert(separators, get())\
6377 else\
6378 break\
6379 end\
6380 end\
6381 local cbrace = expect('Symbol', '}')\
6382 return MkNode{\
6383 Type = 'TableLiteral';\
6384 EntryList = entries;\
6385 Token_SeparatorList = separators;\
6386 Token_OpenBrace = obrace;\
6387 Token_CloseBrace = cbrace;\
6388 GetFirstToken = function(self)\
6389 return self.Token_OpenBrace\
6390 end;\
6391 GetLastToken = function(self)\
6392 return self.Token_CloseBrace\
6393 end;\
6394 }\
6395 end\
6396\
6397 -- List of identifiers\
6398 local function varlist()\
6399 local varList = {}\
6400 local commaList = {}\
6401 if peek().Type == 'Ident' then\
6402 table.insert(varList, get())\
6403 end\
6404 while peek().Source == ',' do\
6405 table.insert(commaList, get())\
6406 local id = expect('Ident')\
6407 table.insert(varList, id)\
6408 end\
6409 return varList, commaList\
6410 end\
6411\
6412 -- Body\
6413 local function blockbody(terminator)\
6414 local body = block()\
6415 local after = peek()\
6416 if after.Type == 'Keyword' and after.Source == terminator then\
6417 get()\
6418 return body, after\
6419 else\
6420 print(after.Type, after.Source)\
6421 error(getTokenStartPosition(after)..\": \"..terminator..\" expected.\")\
6422 end\
6423 end\
6424\
6425 -- Function declaration\
6426 local function funcdecl(isAnonymous)\
6427 local functionKw = get()\
6428 --\
6429 local nameChain;\
6430 local nameChainSeparator;\
6431 --\
6432 if not isAnonymous then\
6433 nameChain = {}\
6434 nameChainSeparator = {}\
6435 --\
6436 table.insert(nameChain, expect('Ident'))\
6437 --\
6438 while peek().Source == '.' do\
6439 table.insert(nameChainSeparator, get())\
6440 table.insert(nameChain, expect('Ident'))\
6441 end\
6442 if peek().Source == ':' then\
6443 table.insert(nameChainSeparator, get())\
6444 table.insert(nameChain, expect('Ident'))\
6445 end\
6446 end\
6447 --\
6448 local oparenTk = expect('Symbol', '(')\
6449 local argList, argCommaList = varlist()\
6450 local cparenTk = expect('Symbol', ')')\
6451 local fbody, enTk = blockbody('end')\
6452 --\
6453 return MkNode{\
6454 Type = (isAnonymous and 'FunctionLiteral' or 'FunctionStat');\
6455 NameChain = nameChain;\
6456 ArgList = argList;\
6457 Body = fbody;\
6458 --\
6459 Token_Function = functionKw;\
6460 Token_NameChainSeparator = nameChainSeparator;\
6461 Token_OpenParen = oparenTk;\
6462 Token_ArgCommaList = argCommaList;\
6463 Token_CloseParen = cparenTk;\
6464 Token_End = enTk;\
6465 GetFirstToken = function(self)\
6466 return self.Token_Function\
6467 end;\
6468 GetLastToken = function(self)\
6469 return self.Token_End;\
6470 end;\
6471 }\
6472 end\
6473\
6474 -- Argument list passed to a funciton\
6475 local function functionargs()\
6476 local tk = peek()\
6477 if tk.Source == '(' then\
6478 local oparenTk = get()\
6479 local argList = {}\
6480 local argCommaList = {}\
6481 while peek().Source ~= ')' do\
6482 table.insert(argList, expr())\
6483 if peek().Source == ',' then\
6484 table.insert(argCommaList, get())\
6485 else\
6486 break\
6487 end\
6488 end\
6489 local cparenTk = expect('Symbol', ')')\
6490 return MkNode{\
6491 CallType = 'ArgCall';\
6492 ArgList = argList;\
6493 --\
6494 Token_CommaList = argCommaList;\
6495 Token_OpenParen = oparenTk;\
6496 Token_CloseParen = cparenTk;\
6497 GetFirstToken = function(self)\
6498 return self.Token_OpenParen\
6499 end;\
6500 GetLastToken = function(self)\
6501 return self.Token_CloseParen\
6502 end;\
6503 }\
6504 elseif tk.Source == '{' then\
6505 return MkNode{\
6506 CallType = 'TableCall';\
6507 TableExpr = expr();\
6508 GetFirstToken = function(self)\
6509 return self.TableExpr:GetFirstToken()\
6510 end;\
6511 GetLastToken = function(self)\
6512 return self.TableExpr:GetLastToken()\
6513 end;\
6514 }\
6515 elseif tk.Type == 'String' then\
6516 return MkNode{\
6517 CallType = 'StringCall';\
6518 Token = get();\
6519 GetFirstToken = function(self)\
6520 return self.Token\
6521 end;\
6522 GetLastToken = function(self)\
6523 return self.Token\
6524 end;\
6525 }\
6526 else\
6527 error(\"Function arguments expected.\")\
6528 end\
6529 end\
6530\
6531 local function primaryexpr()\
6532 local base = prefixexpr()\
6533 assert(base, \"nil prefixexpr\")\
6534 while true do\
6535 local tk = peek()\
6536 if tk.Source == '.' then\
6537 local dotTk = get()\
6538 local fieldName = expect('Ident')\
6539 base = MkNode{\
6540 Type = 'FieldExpr';\
6541 Base = base;\
6542 Field = fieldName;\
6543 Token_Dot = dotTk;\
6544 GetFirstToken = function(self)\
6545 return self.Base:GetFirstToken()\
6546 end;\
6547 GetLastToken = function(self)\
6548 return self.Field\
6549 end;\
6550 }\
6551 elseif tk.Source == ':' then\
6552 local colonTk = get()\
6553 local methodName = expect('Ident')\
6554 local fargs = functionargs()\
6555 base = MkNode{\
6556 Type = 'MethodExpr';\
6557 Base = base;\
6558 Method = methodName;\
6559 FunctionArguments = fargs;\
6560 Token_Colon = colonTk;\
6561 GetFirstToken = function(self)\
6562 return self.Base:GetFirstToken()\
6563 end;\
6564 GetLastToken = function(self)\
6565 return self.FunctionArguments:GetLastToken()\
6566 end;\
6567 }\
6568 elseif tk.Source == '[' then\
6569 local obrac = get()\
6570 local index = expr()\
6571 local cbrac = expect('Symbol', ']')\
6572 base = MkNode{\
6573 Type = 'IndexExpr';\
6574 Base = base;\
6575 Index = index;\
6576 Token_OpenBracket = obrac;\
6577 Token_CloseBracket = cbrac;\
6578 GetFirstToken = function(self)\
6579 return self.Base:GetFirstToken()\
6580 end;\
6581 GetLastToken = function(self)\
6582 return self.Token_CloseBracket\
6583 end;\
6584 }\
6585 elseif tk.Source == '{' then\
6586 base = MkNode{\
6587 Type = 'CallExpr';\
6588 Base = base;\
6589 FunctionArguments = functionargs();\
6590 GetFirstToken = function(self)\
6591 return self.Base:GetFirstToken()\
6592 end;\
6593 GetLastToken = function(self)\
6594 return self.FunctionArguments:GetLastToken()\
6595 end;\
6596 }\
6597 elseif tk.Source == '(' then\
6598 base = MkNode{\
6599 Type = 'CallExpr';\
6600 Base = base;\
6601 FunctionArguments = functionargs();\
6602 GetFirstToken = function(self)\
6603 return self.Base:GetFirstToken()\
6604 end;\
6605 GetLastToken = function(self)\
6606 return self.FunctionArguments:GetLastToken()\
6607 end;\
6608 }\
6609 else\
6610 return base\
6611 end\
6612 end\
6613 end\
6614\
6615 local function simpleexpr()\
6616 local tk = peek()\
6617 if tk.Type == 'Number' then\
6618 return MkNode{\
6619 Type = 'NumberLiteral';\
6620 Token = get();\
6621 GetFirstToken = function(self)\
6622 return self.Token\
6623 end;\
6624 GetLastToken = function(self)\
6625 return self.Token\
6626 end;\
6627 }\
6628 elseif tk.Type == 'String' then\
6629 return MkNode{\
6630 Type = 'StringLiteral';\
6631 Token = get();\
6632 GetFirstToken = function(self)\
6633 return self.Token\
6634 end;\
6635 GetLastToken = function(self)\
6636 return self.Token\
6637 end;\
6638 }\
6639 elseif tk.Source == 'nil' then\
6640 return MkNode{\
6641 Type = 'NilLiteral';\
6642 Token = get();\
6643 GetFirstToken = function(self)\
6644 return self.Token\
6645 end;\
6646 GetLastToken = function(self)\
6647 return self.Token\
6648 end;\
6649 }\
6650 elseif tk.Source == 'true' or tk.Source == 'false' then\
6651 return MkNode{\
6652 Type = 'BooleanLiteral';\
6653 Token = get();\
6654 GetFirstToken = function(self)\
6655 return self.Token\
6656 end;\
6657 GetLastToken = function(self)\
6658 return self.Token\
6659 end;\
6660 }\
6661 elseif tk.Source == '...' then\
6662 return MkNode{\
6663 Type = 'VargLiteral';\
6664 Token = get();\
6665 GetFirstToken = function(self)\
6666 return self.Token\
6667 end;\
6668 GetLastToken = function(self)\
6669 return self.Token\
6670 end;\
6671 }\
6672 elseif tk.Source == '{' then\
6673 return tableexpr()\
6674 elseif tk.Source == 'function' then\
6675 return funcdecl(true)\
6676 else\
6677 return primaryexpr()\
6678 end\
6679 end\
6680\
6681 local function subexpr(limit)\
6682 local curNode;\
6683\
6684 -- Initial Base Expression\
6685 if isUnop() then\
6686 local opTk = get()\
6687 local ex = subexpr(UnaryPriority)\
6688 curNode = MkNode{\
6689 Type = 'UnopExpr';\
6690 Token_Op = opTk;\
6691 Rhs = ex;\
6692 GetFirstToken = function(self)\
6693 return self.Token_Op\
6694 end;\
6695 GetLastToken = function(self)\
6696 return self.Rhs:GetLastToken()\
6697 end;\
6698 }\
6699 else\
6700 curNode = simpleexpr()\
6701 assert(curNode, \"nil simpleexpr\")\
6702 end\
6703\
6704 -- Apply Precedence Recursion Chain\
6705 while isBinop() and BinaryPriority[peek().Source][1] > limit do\
6706 local opTk = get()\
6707 local rhs = subexpr(BinaryPriority[opTk.Source][2])\
6708 assert(rhs, \"RhsNeeded\")\
6709 curNode = MkNode{\
6710 Type = 'BinopExpr';\
6711 Lhs = curNode;\
6712 Rhs = rhs;\
6713 Token_Op = opTk;\
6714 GetFirstToken = function(self)\
6715 return self.Lhs:GetFirstToken()\
6716 end;\
6717 GetLastToken = function(self)\
6718 return self.Rhs:GetLastToken()\
6719 end;\
6720 }\
6721 end\
6722\
6723 -- Return result\
6724 return curNode\
6725 end\
6726\
6727 -- Expression\
6728 expr = function()\
6729 return subexpr(0)\
6730 end\
6731\
6732 -- Expression statement\
6733 local function exprstat()\
6734 local ex = primaryexpr()\
6735 if ex.Type == 'MethodExpr' or ex.Type == 'CallExpr' then\
6736 -- all good, calls can be statements\
6737 return MkNode{\
6738 Type = 'CallExprStat';\
6739 Expression = ex;\
6740 GetFirstToken = function(self)\
6741 return self.Expression:GetFirstToken()\
6742 end;\
6743 GetLastToken = function(self)\
6744 return self.Expression:GetLastToken()\
6745 end;\
6746 }\
6747 else\
6748 -- Assignment expr\
6749 local lhs = {ex}\
6750 local lhsSeparator = {}\
6751 while peek().Source == ',' do\
6752 table.insert(lhsSeparator, get())\
6753 local lhsPart = primaryexpr()\
6754 if lhsPart.Type == 'MethodExpr' or lhsPart.Type == 'CallExpr' then\
6755 error(\"Bad left hand side of assignment\")\
6756 end\
6757 table.insert(lhs, lhsPart)\
6758 end\
6759 local eq = expect('Symbol', '=')\
6760 local rhs = {expr()}\
6761 local rhsSeparator = {}\
6762 while peek().Source == ',' do\
6763 table.insert(rhsSeparator, get())\
6764 table.insert(rhs, expr())\
6765 end\
6766 return MkNode{\
6767 Type = 'AssignmentStat';\
6768 Rhs = rhs;\
6769 Lhs = lhs;\
6770 Token_Equals = eq;\
6771 Token_LhsSeparatorList = lhsSeparator;\
6772 Token_RhsSeparatorList = rhsSeparator;\
6773 GetFirstToken = function(self)\
6774 return self.Lhs[1]:GetFirstToken()\
6775 end;\
6776 GetLastToken = function(self)\
6777 return self.Rhs[#self.Rhs]:GetLastToken()\
6778 end;\
6779 }\
6780 end\
6781 end\
6782\
6783 -- If statement\
6784 local function ifstat()\
6785 local ifKw = get()\
6786 local condition = expr()\
6787 local thenKw = expect('Keyword', 'then')\
6788 local ifBody = block()\
6789 local elseClauses = {}\
6790 while peek().Source == 'elseif' or peek().Source == 'else' do\
6791 local elseifKw = get()\
6792 local elseifCondition, elseifThenKw;\
6793 if elseifKw.Source == 'elseif' then\
6794 elseifCondition = expr()\
6795 elseifThenKw = expect('Keyword', 'then')\
6796 end\
6797 local elseifBody = block()\
6798 table.insert(elseClauses, {\
6799 Condition = elseifCondition;\
6800 Body = elseifBody;\
6801 --\
6802 ClauseType = elseifKw.Source;\
6803 Token = elseifKw;\
6804 Token_Then = elseifThenKw;\
6805 })\
6806 if elseifKw.Source == 'else' then\
6807 break\
6808 end\
6809 end\
6810 local enKw = expect('Keyword', 'end')\
6811 return MkNode{\
6812 Type = 'IfStat';\
6813 Condition = condition;\
6814 Body = ifBody;\
6815 ElseClauseList = elseClauses;\
6816 --\
6817 Token_If = ifKw;\
6818 Token_Then = thenKw;\
6819 Token_End = enKw;\
6820 GetFirstToken = function(self)\
6821 return self.Token_If\
6822 end;\
6823 GetLastToken = function(self)\
6824 return self.Token_End\
6825 end;\
6826 }\
6827 end\
6828\
6829 -- Do statement\
6830 local function dostat()\
6831 local doKw = get()\
6832 local body, enKw = blockbody('end')\
6833 --\
6834 return MkNode{\
6835 Type = 'DoStat';\
6836 Body = body;\
6837 --\
6838 Token_Do = doKw;\
6839 Token_End = enKw;\
6840 GetFirstToken = function(self)\
6841 return self.Token_Do\
6842 end;\
6843 GetLastToken = function(self)\
6844 return self.Token_End\
6845 end;\
6846 }\
6847 end\
6848\
6849 -- While statement\
6850 local function whilestat()\
6851 local whileKw = get()\
6852 local condition = expr()\
6853 local doKw = expect('Keyword', 'do')\
6854 local body, enKw = blockbody('end')\
6855 --\
6856 return MkNode{\
6857 Type = 'WhileStat';\
6858 Condition = condition;\
6859 Body = body;\
6860 --\
6861 Token_While = whileKw;\
6862 Token_Do = doKw;\
6863 Token_End = enKw;\
6864 GetFirstToken = function(self)\
6865 return self.Token_While\
6866 end;\
6867 GetLastToken = function(self)\
6868 return self.Token_End\
6869 end;\
6870 }\
6871 end\
6872\
6873 -- For statement\
6874 local function forstat()\
6875 local forKw = get()\
6876 local loopVars, loopVarCommas = varlist()\
6877 --local node = {}\
6878 if peek().Source == '=' then\
6879 local eqTk = get()\
6880 local exprList, exprCommaList = exprlist()\
6881 if #exprList < 2 or #exprList > 3 then\
6882 error(\"expected 2 or 3 values for range bounds\")\
6883 end\
6884 local doTk = expect('Keyword', 'do')\
6885 local body, enTk = blockbody('end')\
6886 return MkNode{\
6887 Type = 'NumericForStat';\
6888 VarList = loopVars;\
6889 RangeList = exprList;\
6890 Body = body;\
6891 --\
6892 Token_For = forKw;\
6893 Token_VarCommaList = loopVarCommas;\
6894 Token_Equals = eqTk;\
6895 Token_RangeCommaList = exprCommaList;\
6896 Token_Do = doTk;\
6897 Token_End = enTk;\
6898 GetFirstToken = function(self)\
6899 return self.Token_For\
6900 end;\
6901 GetLastToken = function(self)\
6902 return self.Token_End\
6903 end;\
6904 }\
6905 elseif peek().Source == 'in' then\
6906 local inTk = get()\
6907 local exprList, exprCommaList = exprlist()\
6908 local doTk = expect('Keyword', 'do')\
6909 local body, enTk = blockbody('end')\
6910 return MkNode{\
6911 Type = 'GenericForStat';\
6912 VarList = loopVars;\
6913 GeneratorList = exprList;\
6914 Body = body;\
6915 --\
6916 Token_For = forKw;\
6917 Token_VarCommaList = loopVarCommas;\
6918 Token_In = inTk;\
6919 Token_GeneratorCommaList = exprCommaList;\
6920 Token_Do = doTk;\
6921 Token_End = enTk;\
6922 GetFirstToken = function(self)\
6923 return self.Token_For\
6924 end;\
6925 GetLastToken = function(self)\
6926 return self.Token_End\
6927 end;\
6928 }\
6929 else\
6930 error(\"`=` or in expected\")\
6931 end\
6932 end\
6933\
6934 -- Repeat statement\
6935 local function repeatstat()\
6936 local repeatKw = get()\
6937 local body, untilTk = blockbody('until')\
6938 local condition = expr()\
6939 return MkNode{\
6940 Type = 'RepeatStat';\
6941 Body = body;\
6942 Condition = condition;\
6943 --\
6944 Token_Repeat = repeatKw;\
6945 Token_Until = untilTk;\
6946 GetFirstToken = function(self)\
6947 return self.Token_Repeat\
6948 end;\
6949 GetLastToken = function(self)\
6950 return self.Condition:GetLastToken()\
6951 end;\
6952 }\
6953 end\
6954\
6955 -- Local var declaration\
6956 local function localdecl()\
6957 local localKw = get()\
6958 if peek().Source == 'function' then\
6959 -- Local function def\
6960 local funcStat = funcdecl(false)\
6961 if #funcStat.NameChain > 1 then\
6962 error(getTokenStartPosition(funcStat.Token_NameChainSeparator[1])..\": `(` expected.\")\
6963 end\
6964 return MkNode{\
6965 Type = 'LocalFunctionStat';\
6966 FunctionStat = funcStat;\
6967 Token_Local = localKw;\
6968 GetFirstToken = function(self)\
6969 return self.Token_Local\
6970 end;\
6971 GetLastToken = function(self)\
6972 return self.FunctionStat:GetLastToken()\
6973 end;\
6974 }\
6975 elseif peek().Type == 'Ident' then\
6976 -- Local variable declaration\
6977 local varList, varCommaList = varlist()\
6978 local exprList, exprCommaList = {}, {}\
6979 local eqToken;\
6980 if peek().Source == '=' then\
6981 eqToken = get()\
6982 exprList, exprCommaList = exprlist()\
6983 end\
6984 return MkNode{\
6985 Type = 'LocalVarStat';\
6986 VarList = varList;\
6987 ExprList = exprList;\
6988 Token_Local = localKw;\
6989 Token_Equals = eqToken;\
6990 Token_VarCommaList = varCommaList;\
6991 Token_ExprCommaList = exprCommaList;\
6992 GetFirstToken = function(self)\
6993 return self.Token_Local\
6994 end;\
6995 GetLastToken = function(self)\
6996 if #self.ExprList > 0 then\
6997 return self.ExprList[#self.ExprList]:GetLastToken()\
6998 else\
6999 return self.VarList[#self.VarList]\
7000 end\
7001 end;\
7002 }\
7003 else\
7004 error(\"`function` or ident expected\")\
7005 end\
7006 end\
7007\
7008 -- Return statement\
7009 local function retstat()\
7010 local returnKw = get()\
7011 local exprList;\
7012 local commaList;\
7013 if isBlockFollow() or peek().Source == ';' then\
7014 exprList = {}\
7015 commaList = {}\
7016 else\
7017 exprList, commaList = exprlist()\
7018 end\
7019 return {\
7020 Type = 'ReturnStat';\
7021 ExprList = exprList;\
7022 Token_Return = returnKw;\
7023 Token_CommaList = commaList;\
7024 GetFirstToken = function(self)\
7025 return self.Token_Return\
7026 end;\
7027 GetLastToken = function(self)\
7028 if #self.ExprList > 0 then\
7029 return self.ExprList[#self.ExprList]:GetLastToken()\
7030 else\
7031 return self.Token_Return\
7032 end\
7033 end;\
7034 }\
7035 end\
7036\
7037 -- Break statement\
7038 local function breakstat()\
7039 local breakKw = get()\
7040 return {\
7041 Type = 'BreakStat';\
7042 Token_Break = breakKw;\
7043 GetFirstToken = function(self)\
7044 return self.Token_Break\
7045 end;\
7046 GetLastToken = function(self)\
7047 return self.Token_Break\
7048 end;\
7049 }\
7050 end\
7051\
7052 -- Expression\
7053 local function statement()\
7054 local tok = peek()\
7055 if tok.Source == 'if' then\
7056 return false, ifstat()\
7057 elseif tok.Source == 'while' then\
7058 return false, whilestat()\
7059 elseif tok.Source == 'do' then\
7060 return false, dostat()\
7061 elseif tok.Source == 'for' then\
7062 return false, forstat()\
7063 elseif tok.Source == 'repeat' then\
7064 return false, repeatstat()\
7065 elseif tok.Source == 'function' then\
7066 return false, funcdecl(false)\
7067 elseif tok.Source == 'local' then\
7068 return false, localdecl()\
7069 elseif tok.Source == 'return' then\
7070 return true, retstat()\
7071 elseif tok.Source == 'break' then\
7072 return true, breakstat()\
7073 else\
7074 return false, exprstat()\
7075 end\
7076 end\
7077\
7078 -- Chunk\
7079 block = function()\
7080 local statements = {}\
7081 local semicolons = {}\
7082 local isLast = false\
7083 while not isLast and not isBlockFollow() do\
7084 -- Parse statement\
7085 local stat;\
7086 isLast, stat = statement()\
7087 table.insert(statements, stat)\
7088 local next = peek()\
7089 if next.Type == 'Symbol' and next.Source == ';' then\
7090 semicolons[#statements] = get()\
7091 end\
7092 end\
7093 return {\
7094 Type = 'StatList';\
7095 StatementList = statements;\
7096 SemicolonList = semicolons;\
7097 GetFirstToken = function(self)\
7098 if #self.StatementList == 0 then\
7099 return nil\
7100 else\
7101 return self.StatementList[1]:GetFirstToken()\
7102 end\
7103 end;\
7104 GetLastToken = function(self)\
7105 if #self.StatementList == 0 then\
7106 return nil\
7107 elseif self.SemicolonList[#self.StatementList] then\
7108 -- Last token may be one of the semicolon separators\
7109 return self.SemicolonList[#self.StatementList]\
7110 else\
7111 return self.StatementList[#self.StatementList]:GetLastToken()\
7112 end\
7113 end;\
7114 }\
7115 end\
7116\
7117 return block()\
7118end\
7119\
7120local function VisitAst(ast, visitors)\
7121 local ExprType = lookupify{\
7122 'BinopExpr'; 'UnopExpr';\
7123 'NumberLiteral'; 'StringLiteral'; 'NilLiteral'; 'BooleanLiteral'; 'VargLiteral';\
7124 'FieldExpr'; 'IndexExpr';\
7125 'MethodExpr'; 'CallExpr';\
7126 'FunctionLiteral';\
7127 'VariableExpr';\
7128 'ParenExpr';\
7129 'TableLiteral';\
7130 }\
7131\
7132 local StatType = lookupify{\
7133 'StatList';\
7134 'BreakStat';\
7135 'ReturnStat';\
7136 'LocalVarStat';\
7137 'LocalFunctionStat';\
7138 'FunctionStat';\
7139 'RepeatStat';\
7140 'GenericForStat';\
7141 'NumericForStat';\
7142 'WhileStat';\
7143 'DoStat';\
7144 'IfStat';\
7145 'CallExprStat';\
7146 'AssignmentStat';\
7147 }\
7148\
7149 -- Check for typos in visitor construction\
7150 for visitorSubject in pairs(visitors) do\
7151 if not StatType[visitorSubject] and not ExprType[visitorSubject] then\
7152 error(\"Invalid visitor target: `\"..visitorSubject..\"`\")\
7153 end\
7154 end\
7155\
7156 -- Helpers to call visitors on a node\
7157 local function preVisit(exprOrStat)\
7158 local visitor = visitors[exprOrStat.Type]\
7159 if type(visitor) == 'function' then\
7160 return visitor(exprOrStat)\
7161 elseif visitor and visitor.Pre then\
7162 return visitor.Pre(exprOrStat)\
7163 end\
7164 end\
7165 local function postVisit(exprOrStat)\
7166 local visitor = visitors[exprOrStat.Type]\
7167 if visitor and type(visitor) == 'table' and visitor.Post then\
7168 return visitor.Post(exprOrStat)\
7169 end\
7170 end\
7171\
7172 local visitExpr, visitStat;\
7173\
7174 visitExpr = function(expr)\
7175 if preVisit(expr) then\
7176 -- Handler did custom child iteration or blocked child iteration\
7177 return\
7178 end\
7179 if expr.Type == 'BinopExpr' then\
7180 visitExpr(expr.Lhs)\
7181 visitExpr(expr.Rhs)\
7182 elseif expr.Type == 'UnopExpr' then\
7183 visitExpr(expr.Rhs)\
7184 elseif expr.Type == 'NumberLiteral' or expr.Type == 'StringLiteral' or\
7185 expr.Type == 'NilLiteral' or expr.Type == 'BooleanLiteral' or\
7186 expr.Type == 'VargLiteral'\
7187 then\
7188 -- No children to visit, single token literals\
7189 elseif expr.Type == 'FieldExpr' then\
7190 visitExpr(expr.Base)\
7191 elseif expr.Type == 'IndexExpr' then\
7192 visitExpr(expr.Base)\
7193 visitExpr(expr.Index)\
7194 elseif expr.Type == 'MethodExpr' or expr.Type == 'CallExpr' then\
7195 visitExpr(expr.Base)\
7196 if expr.FunctionArguments.CallType == 'ArgCall' then\
7197 for _, argExpr in pairs(expr.FunctionArguments.ArgList) do\
7198 visitExpr(argExpr)\
7199 end\
7200 elseif expr.FunctionArguments.CallType == 'TableCall' then\
7201 visitExpr(expr.FunctionArguments.TableExpr)\
7202 end\
7203 elseif expr.Type == 'FunctionLiteral' then\
7204 visitStat(expr.Body)\
7205 elseif expr.Type == 'VariableExpr' then\
7206 -- No children to visit\
7207 elseif expr.Type == 'ParenExpr' then\
7208 visitExpr(expr.Expression)\
7209 elseif expr.Type == 'TableLiteral' then\
7210 for _, entry in pairs(expr.EntryList) do\
7211 if entry.EntryType == 'Field' then\
7212 visitExpr(entry.Value)\
7213 elseif entry.EntryType == 'Index' then\
7214 visitExpr(entry.Index)\
7215 visitExpr(entry.Value)\
7216 elseif entry.EntryType == 'Value' then\
7217 visitExpr(entry.Value)\
7218 else\
7219 assert(false, \"unreachable\")\
7220 end\
7221 end\
7222 else\
7223 assert(false, \"unreachable, type: \"..expr.Type..\":\"..FormatTable(expr))\
7224 end\
7225 postVisit(expr)\
7226 end\
7227\
7228 visitStat = function(stat)\
7229 if preVisit(stat) then\
7230 -- Handler did custom child iteration or blocked child iteration\
7231 return\
7232 end\
7233 if stat.Type == 'StatList' then\
7234 for _, ch in pairs(stat.StatementList) do\
7235 visitStat(ch)\
7236 end\
7237 elseif stat.Type == 'BreakStat' then\
7238 -- No children to visit\
7239 elseif stat.Type == 'ReturnStat' then\
7240 for _, expr in pairs(stat.ExprList) do\
7241 visitExpr(expr)\
7242 end\
7243 elseif stat.Type == 'LocalVarStat' then\
7244 if stat.Token_Equals then\
7245 for _, expr in pairs(stat.ExprList) do\
7246 visitExpr(expr)\
7247 end\
7248 end\
7249 elseif stat.Type == 'LocalFunctionStat' then\
7250 visitStat(stat.FunctionStat.Body)\
7251 elseif stat.Type == 'FunctionStat' then\
7252 visitStat(stat.Body)\
7253 elseif stat.Type == 'RepeatStat' then\
7254 visitStat(stat.Body)\
7255 visitExpr(stat.Condition)\
7256 elseif stat.Type == 'GenericForStat' then\
7257 for _, expr in pairs(stat.GeneratorList) do\
7258 visitExpr(expr)\
7259 end\
7260 visitStat(stat.Body)\
7261 elseif stat.Type == 'NumericForStat' then\
7262 for _, expr in pairs(stat.RangeList) do\
7263 visitExpr(expr)\
7264 end\
7265 visitStat(stat.Body)\
7266 elseif stat.Type == 'WhileStat' then\
7267 visitExpr(stat.Condition)\
7268 visitStat(stat.Body)\
7269 elseif stat.Type == 'DoStat' then\
7270 visitStat(stat.Body)\
7271 elseif stat.Type == 'IfStat' then\
7272 visitExpr(stat.Condition)\
7273 visitStat(stat.Body)\
7274 for _, clause in pairs(stat.ElseClauseList) do\
7275 if clause.Condition then\
7276 visitExpr(clause.Condition)\
7277 end\
7278 visitStat(clause.Body)\
7279 end\
7280 elseif stat.Type == 'CallExprStat' then\
7281 visitExpr(stat.Expression)\
7282 elseif stat.Type == 'AssignmentStat' then\
7283 for _, ex in pairs(stat.Lhs) do\
7284 visitExpr(ex)\
7285 end\
7286 for _, ex in pairs(stat.Rhs) do\
7287 visitExpr(ex)\
7288 end\
7289 else\
7290 assert(false, \"unreachable\")\
7291 end\
7292 postVisit(stat)\
7293 end\
7294\
7295 if StatType[ast.Type] then\
7296 visitStat(ast)\
7297 else\
7298 visitExpr(ast)\
7299 end\
7300end\
7301\
7302local function AddVariableInfo(ast)\
7303 local globalVars = {}\
7304 local currentScope = nil\
7305\
7306 -- Numbering generator for variable lifetimes\
7307 local locationGenerator = 0\
7308 local function markLocation()\
7309 locationGenerator = locationGenerator + 1\
7310 return locationGenerator\
7311 end\
7312\
7313 -- Scope management\
7314 local function pushScope()\
7315 currentScope = {\
7316 ParentScope = currentScope;\
7317 ChildScopeList = {};\
7318 VariableList = {};\
7319 BeginLocation = markLocation();\
7320 }\
7321 if currentScope.ParentScope then\
7322 currentScope.Depth = currentScope.ParentScope.Depth + 1\
7323 table.insert(currentScope.ParentScope.ChildScopeList, currentScope)\
7324 else\
7325 currentScope.Depth = 1\
7326 end\
7327 function currentScope:GetVar(varName)\
7328 for _, var in pairs(self.VariableList) do\
7329 if var.Name == varName then\
7330 return var\
7331 end\
7332 end\
7333 if self.ParentScope then\
7334 return self.ParentScope:GetVar(varName)\
7335 else\
7336 for _, var in pairs(globalVars) do\
7337 if var.Name == varName then\
7338 return var\
7339 end\
7340 end\
7341 end\
7342 end\
7343 end\
7344 local function popScope()\
7345 local scope = currentScope\
7346\
7347 -- Mark where this scope ends\
7348 scope.EndLocation = markLocation()\
7349\
7350 -- Mark all of the variables in the scope as ending there\
7351 for _, var in pairs(scope.VariableList) do\
7352 var.ScopeEndLocation = scope.EndLocation\
7353 end\
7354\
7355 -- Move to the parent scope\
7356 currentScope = scope.ParentScope\
7357\
7358 return scope\
7359 end\
7360 pushScope() -- push initial scope\
7361\
7362 -- Add / reference variables\
7363 local function addLocalVar(name, setNameFunc, localInfo)\
7364 assert(localInfo, \"Misisng localInfo\")\
7365 assert(name, \"Missing local var name\")\
7366 local var = {\
7367 Type = 'Local';\
7368 Name = name;\
7369 RenameList = {setNameFunc};\
7370 AssignedTo = false;\
7371 Info = localInfo;\
7372 UseCount = 0;\
7373 Scope = currentScope;\
7374 BeginLocation = markLocation();\
7375 EndLocation = markLocation();\
7376 ReferenceLocationList = {markLocation()};\
7377 }\
7378 function var:Rename(newName)\
7379 self.Name = newName\
7380 for _, renameFunc in pairs(self.RenameList) do\
7381 renameFunc(newName)\
7382 end\
7383 end\
7384 function var:Reference()\
7385 self.UseCount = self.UseCount + 1\
7386 end\
7387 table.insert(currentScope.VariableList, var)\
7388 return var\
7389 end\
7390 local function getGlobalVar(name)\
7391 for _, var in pairs(globalVars) do\
7392 if var.Name == name then\
7393 return var\
7394 end\
7395 end\
7396 local var = {\
7397 Type = 'Global';\
7398 Name = name;\
7399 RenameList = {};\
7400 AssignedTo = false;\
7401 UseCount = 0;\
7402 Scope = nil; -- Globals have no scope\
7403 BeginLocation = markLocation();\
7404 EndLocation = markLocation();\
7405 ReferenceLocationList = {};\
7406 }\
7407 function var:Rename(newName)\
7408 self.Name = newName\
7409 for _, renameFunc in pairs(self.RenameList) do\
7410 renameFunc(newName)\
7411 end\
7412 end\
7413 function var:Reference()\
7414 self.UseCount = self.UseCount + 1\
7415 end\
7416 table.insert(globalVars, var)\
7417 return var\
7418 end\
7419 local function addGlobalReference(name, setNameFunc)\
7420 assert(name, \"Missing var name\")\
7421 local var = getGlobalVar(name)\
7422 table.insert(var.RenameList, setNameFunc)\
7423 return var\
7424 end\
7425 local function getLocalVar(scope, name)\
7426 -- First search this scope\
7427 -- Note: Reverse iterate here because Lua does allow shadowing a local\
7428 -- within the same scope, and the later defined variable should\
7429 -- be the one referenced.\
7430 for i = #scope.VariableList, 1, -1 do\
7431 if scope.VariableList[i].Name == name then\
7432 return scope.VariableList[i]\
7433 end\
7434 end\
7435\
7436 -- Then search parent scope\
7437 if scope.ParentScope then\
7438 local var = getLocalVar(scope.ParentScope, name)\
7439 if var then\
7440 return var\
7441 end\
7442 end\
7443\
7444 -- Then\
7445 return nil\
7446 end\
7447 local function referenceVariable(name, setNameFunc)\
7448 assert(name, \"Missing var name\")\
7449 local var = getLocalVar(currentScope, name)\
7450 if var then\
7451 table.insert(var.RenameList, setNameFunc)\
7452 else\
7453 var = addGlobalReference(name, setNameFunc)\
7454 end\
7455 -- Update the end location of where this variable is used, and\
7456 -- add this location to the list of references to this variable.\
7457 local curLocation = markLocation()\
7458 var.EndLocation = curLocation\
7459 table.insert(var.ReferenceLocationList, var.EndLocation)\
7460 return var\
7461 end\
7462\
7463 local visitor = {}\
7464 visitor.FunctionLiteral = {\
7465 -- Function literal adds a new scope and adds the function literal arguments\
7466 -- as local variables in the scope.\
7467 Pre = function(expr)\
7468 pushScope()\
7469 for index, ident in pairs(expr.ArgList) do\
7470 addLocalVar(ident.Source, function(name)\
7471 ident.Source = name\
7472 end, {\
7473 Type = 'Argument';\
7474 Index = index;\
7475 })\
7476 end\
7477 end;\
7478 Post = function()\
7479 popScope()\
7480 end;\
7481 }\
7482 visitor.VariableExpr = function(expr)\
7483 -- Variable expression references from existing local varibales\
7484 -- in the current scope, annotating the variable usage with variable\
7485 -- information.\
7486 expr.Variable = referenceVariable(expr.Token.Source, function(newName)\
7487 expr.Token.Source = newName\
7488 end)\
7489 end\
7490 visitor.StatList = {\
7491 -- StatList adds a new scope\
7492 Pre = function()\
7493 pushScope()\
7494 end;\
7495 Post = function()\
7496 popScope()\
7497 end;\
7498 }\
7499 visitor.LocalVarStat = {\
7500 Post = function(stat)\
7501 -- Local var stat adds the local variables to the current scope as locals\
7502 -- We need to visit the subexpressions first, because these new locals\
7503 -- will not be in scope for the initialization value expressions. That is:\
7504 -- `local bar = bar + 1`\
7505 -- Is valid code\
7506 for varNum, ident in pairs(stat.VarList) do\
7507 addLocalVar(ident.Source, function(name)\
7508 stat.VarList[varNum].Source = name\
7509 end, {\
7510 Type = 'Local';\
7511 })\
7512 end\
7513 end;\
7514 }\
7515 visitor.LocalFunctionStat = {\
7516 Pre = function(stat)\
7517 -- Local function stat adds the function itself to the current scope as\
7518 -- a local variable, and creates a new scope with the function arguments\
7519 -- as local variables.\
7520 addLocalVar(stat.FunctionStat.NameChain[1].Source, function(name)\
7521 stat.FunctionStat.NameChain[1].Source = name\
7522 end, {\
7523 Type = 'LocalFunction';\
7524 })\
7525 pushScope()\
7526 for index, ident in pairs(stat.FunctionStat.ArgList) do\
7527 addLocalVar(ident.Source, function(name)\
7528 ident.Source = name\
7529 end, {\
7530 Type = 'Argument';\
7531 Index = index;\
7532 })\
7533 end\
7534 end;\
7535 Post = function()\
7536 popScope()\
7537 end;\
7538 }\
7539 visitor.FunctionStat = {\
7540 Pre = function(stat)\
7541 -- Function stat adds a new scope containing the function arguments\
7542 -- as local variables.\
7543 -- A function stat may also assign to a global variable if it is in\
7544 -- the form `function foo()` with no additional dots/colons in the\
7545 -- name chain.\
7546 local nameChain = stat.NameChain\
7547 local var;\
7548 if #nameChain == 1 then\
7549 -- If there is only one item in the name chain, then the first item\
7550 -- is a reference to a global variable.\
7551 var = addGlobalReference(nameChain[1].Source, function(name)\
7552 nameChain[1].Source = name\
7553 end)\
7554 else\
7555 var = referenceVariable(nameChain[1].Source, function(name)\
7556 nameChain[1].Source = name\
7557 end)\
7558 end\
7559 var.AssignedTo = true\
7560 pushScope()\
7561 for index, ident in pairs(stat.ArgList) do\
7562 addLocalVar(ident.Source, function(name)\
7563 ident.Source = name\
7564 end, {\
7565 Type = 'Argument';\
7566 Index = index;\
7567 })\
7568 end\
7569 end;\
7570 Post = function()\
7571 popScope()\
7572 end;\
7573 }\
7574 visitor.GenericForStat = {\
7575 Pre = function(stat)\
7576 -- Generic fors need an extra scope holding the range variables\
7577 -- Need a custom visitor so that the generator expressions can be\
7578 -- visited before we push a scope, but the body can be visited\
7579 -- after we push a scope.\
7580 for _, ex in pairs(stat.GeneratorList) do\
7581 VisitAst(ex, visitor)\
7582 end\
7583 pushScope()\
7584 for index, ident in pairs(stat.VarList) do\
7585 addLocalVar(ident.Source, function(name)\
7586 ident.Source = name\
7587 end, {\
7588 Type = 'ForRange';\
7589 Index = index;\
7590 })\
7591 end\
7592 VisitAst(stat.Body, visitor)\
7593 popScope()\
7594 return true -- Custom visit\
7595 end;\
7596 }\
7597 visitor.NumericForStat = {\
7598 Pre = function(stat)\
7599 -- Numeric fors need an extra scope holding the range variables\
7600 -- Need a custom visitor so that the generator expressions can be\
7601 -- visited before we push a scope, but the body can be visited\
7602 -- after we push a scope.\
7603 for _, ex in pairs(stat.RangeList) do\
7604 VisitAst(ex, visitor)\
7605 end\
7606 pushScope()\
7607 for index, ident in pairs(stat.VarList) do\
7608 addLocalVar(ident.Source, function(name)\
7609 ident.Source = name\
7610 end, {\
7611 Type = 'ForRange';\
7612 Index = index;\
7613 })\
7614 end\
7615 VisitAst(stat.Body, visitor)\
7616 popScope()\
7617 return true -- Custom visit\
7618 end;\
7619 }\
7620 visitor.AssignmentStat = {\
7621 Post = function(stat)\
7622 -- For an assignment statement we need to mark the\
7623 -- \"assigned to\" flag on variables.\
7624 for _, ex in pairs(stat.Lhs) do\
7625 if ex.Variable then\
7626 ex.Variable.AssignedTo = true\
7627 end\
7628 end\
7629 end;\
7630 }\
7631\
7632 VisitAst(ast, visitor)\
7633\
7634 return globalVars, popScope()\
7635end\
7636\
7637-- Prints out an AST to a string\
7638local function PrintAst(ast, out)\
7639\
7640 local printStat, printExpr;\
7641\
7642 local function printt(tk)\
7643 if not tk.LeadingWhite or not tk.Source then\
7644 error(\"Bad token: \"..FormatTable(tk))\
7645 end\
7646 table.insert(out, tk.LeadingWhite)\
7647 table.insert(out, tk.Source)\
7648 end\
7649\
7650 printExpr = function(expr)\
7651 if expr.Type == 'BinopExpr' then\
7652 printExpr(expr.Lhs)\
7653 printt(expr.Token_Op)\
7654 printExpr(expr.Rhs)\
7655 elseif expr.Type == 'UnopExpr' then\
7656 printt(expr.Token_Op)\
7657 printExpr(expr.Rhs)\
7658 elseif expr.Type == 'NumberLiteral' or expr.Type == 'StringLiteral' or\
7659 expr.Type == 'NilLiteral' or expr.Type == 'BooleanLiteral' or\
7660 expr.Type == 'VargLiteral'\
7661 then\
7662 -- Just print the token\
7663 printt(expr.Token)\
7664 elseif expr.Type == 'FieldExpr' then\
7665 printExpr(expr.Base)\
7666 printt(expr.Token_Dot)\
7667 printt(expr.Field)\
7668 elseif expr.Type == 'IndexExpr' then\
7669 printExpr(expr.Base)\
7670 printt(expr.Token_OpenBracket)\
7671 printExpr(expr.Index)\
7672 printt(expr.Token_CloseBracket)\
7673 elseif expr.Type == 'MethodExpr' or expr.Type == 'CallExpr' then\
7674 printExpr(expr.Base)\
7675 if expr.Type == 'MethodExpr' then\
7676 printt(expr.Token_Colon)\
7677 printt(expr.Method)\
7678 end\
7679 if expr.FunctionArguments.CallType == 'StringCall' then\
7680 printt(expr.FunctionArguments.Token)\
7681 elseif expr.FunctionArguments.CallType == 'ArgCall' then\
7682 printt(expr.FunctionArguments.Token_OpenParen)\
7683 for index, argExpr in pairs(expr.FunctionArguments.ArgList) do\
7684 printExpr(argExpr)\
7685 local sep = expr.FunctionArguments.Token_CommaList[index]\
7686 if sep then\
7687 printt(sep)\
7688 end\
7689 end\
7690 printt(expr.FunctionArguments.Token_CloseParen)\
7691 elseif expr.FunctionArguments.CallType == 'TableCall' then\
7692 printExpr(expr.FunctionArguments.TableExpr)\
7693 end\
7694 elseif expr.Type == 'FunctionLiteral' then\
7695 printt(expr.Token_Function)\
7696 printt(expr.Token_OpenParen)\
7697 for index, arg in pairs(expr.ArgList) do\
7698 printt(arg)\
7699 local comma = expr.Token_ArgCommaList[index]\
7700 if comma then\
7701 printt(comma)\
7702 end\
7703 end\
7704 printt(expr.Token_CloseParen)\
7705 printStat(expr.Body)\
7706 printt(expr.Token_End)\
7707 elseif expr.Type == 'VariableExpr' then\
7708 printt(expr.Token)\
7709 elseif expr.Type == 'ParenExpr' then\
7710 printt(expr.Token_OpenParen)\
7711 printExpr(expr.Expression)\
7712 printt(expr.Token_CloseParen)\
7713 elseif expr.Type == 'TableLiteral' then\
7714 printt(expr.Token_OpenBrace)\
7715 for index, entry in pairs(expr.EntryList) do\
7716 if entry.EntryType == 'Field' then\
7717 printt(entry.Field)\
7718 printt(entry.Token_Equals)\
7719 printExpr(entry.Value)\
7720 elseif entry.EntryType == 'Index' then\
7721 printt(entry.Token_OpenBracket)\
7722 printExpr(entry.Index)\
7723 printt(entry.Token_CloseBracket)\
7724 printt(entry.Token_Equals)\
7725 printExpr(entry.Value)\
7726 elseif entry.EntryType == 'Value' then\
7727 printExpr(entry.Value)\
7728 else\
7729 assert(false, \"unreachable\")\
7730 end\
7731 local sep = expr.Token_SeparatorList[index]\
7732 if sep then\
7733 printt(sep)\
7734 end\
7735 end\
7736 printt(expr.Token_CloseBrace)\
7737 else\
7738 assert(false, \"unreachable, type: \"..expr.Type..\":\"..FormatTable(expr))\
7739 end\
7740 end\
7741\
7742 printStat = function(stat)\
7743 if stat.Type == 'StatList' then\
7744 for index, ch in pairs(stat.StatementList) do\
7745 printStat(ch)\
7746 if stat.SemicolonList[index] then\
7747 printt(stat.SemicolonList[index])\
7748 end\
7749 end\
7750 elseif stat.Type == 'BreakStat' then\
7751 printt(stat.Token_Break)\
7752 elseif stat.Type == 'ReturnStat' then\
7753 printt(stat.Token_Return)\
7754 for index, expr in pairs(stat.ExprList) do\
7755 printExpr(expr)\
7756 if stat.Token_CommaList[index] then\
7757 printt(stat.Token_CommaList[index])\
7758 end\
7759 end\
7760 elseif stat.Type == 'LocalVarStat' then\
7761 printt(stat.Token_Local)\
7762 for index, var in pairs(stat.VarList) do\
7763 printt(var)\
7764 local comma = stat.Token_VarCommaList[index]\
7765 if comma then\
7766 printt(comma)\
7767 end\
7768 end\
7769 if stat.Token_Equals then\
7770 printt(stat.Token_Equals)\
7771 for index, expr in pairs(stat.ExprList) do\
7772 printExpr(expr)\
7773 local comma = stat.Token_ExprCommaList[index]\
7774 if comma then\
7775 printt(comma)\
7776 end\
7777 end\
7778 end\
7779 elseif stat.Type == 'LocalFunctionStat' then\
7780 printt(stat.Token_Local)\
7781 printt(stat.FunctionStat.Token_Function)\
7782 printt(stat.FunctionStat.NameChain[1])\
7783 printt(stat.FunctionStat.Token_OpenParen)\
7784 for index, arg in pairs(stat.FunctionStat.ArgList) do\
7785 printt(arg)\
7786 local comma = stat.FunctionStat.Token_ArgCommaList[index]\
7787 if comma then\
7788 printt(comma)\
7789 end\
7790 end\
7791 printt(stat.FunctionStat.Token_CloseParen)\
7792 printStat(stat.FunctionStat.Body)\
7793 printt(stat.FunctionStat.Token_End)\
7794 elseif stat.Type == 'FunctionStat' then\
7795 printt(stat.Token_Function)\
7796 for index, part in pairs(stat.NameChain) do\
7797 printt(part)\
7798 local sep = stat.Token_NameChainSeparator[index]\
7799 if sep then\
7800 printt(sep)\
7801 end\
7802 end\
7803 printt(stat.Token_OpenParen)\
7804 for index, arg in pairs(stat.ArgList) do\
7805 printt(arg)\
7806 local comma = stat.Token_ArgCommaList[index]\
7807 if comma then\
7808 printt(comma)\
7809 end\
7810 end\
7811 printt(stat.Token_CloseParen)\
7812 printStat(stat.Body)\
7813 printt(stat.Token_End)\
7814 elseif stat.Type == 'RepeatStat' then\
7815 printt(stat.Token_Repeat)\
7816 printStat(stat.Body)\
7817 printt(stat.Token_Until)\
7818 printExpr(stat.Condition)\
7819 elseif stat.Type == 'GenericForStat' then\
7820 printt(stat.Token_For)\
7821 for index, var in pairs(stat.VarList) do\
7822 printt(var)\
7823 local sep = stat.Token_VarCommaList[index]\
7824 if sep then\
7825 printt(sep)\
7826 end\
7827 end\
7828 printt(stat.Token_In)\
7829 for index, expr in pairs(stat.GeneratorList) do\
7830 printExpr(expr)\
7831 local sep = stat.Token_GeneratorCommaList[index]\
7832 if sep then\
7833 printt(sep)\
7834 end\
7835 end\
7836 printt(stat.Token_Do)\
7837 printStat(stat.Body)\
7838 printt(stat.Token_End)\
7839 elseif stat.Type == 'NumericForStat' then\
7840 printt(stat.Token_For)\
7841 for index, var in pairs(stat.VarList) do\
7842 printt(var)\
7843 local sep = stat.Token_VarCommaList[index]\
7844 if sep then\
7845 printt(sep)\
7846 end\
7847 end\
7848 printt(stat.Token_Equals)\
7849 for index, expr in pairs(stat.RangeList) do\
7850 printExpr(expr)\
7851 local sep = stat.Token_RangeCommaList[index]\
7852 if sep then\
7853 printt(sep)\
7854 end\
7855 end\
7856 printt(stat.Token_Do)\
7857 printStat(stat.Body)\
7858 printt(stat.Token_End)\
7859 elseif stat.Type == 'WhileStat' then\
7860 printt(stat.Token_While)\
7861 printExpr(stat.Condition)\
7862 printt(stat.Token_Do)\
7863 printStat(stat.Body)\
7864 printt(stat.Token_End)\
7865 elseif stat.Type == 'DoStat' then\
7866 printt(stat.Token_Do)\
7867 printStat(stat.Body)\
7868 printt(stat.Token_End)\
7869 elseif stat.Type == 'IfStat' then\
7870 printt(stat.Token_If)\
7871 printExpr(stat.Condition)\
7872 printt(stat.Token_Then)\
7873 printStat(stat.Body)\
7874 for _, clause in pairs(stat.ElseClauseList) do\
7875 printt(clause.Token)\
7876 if clause.Condition then\
7877 printExpr(clause.Condition)\
7878 printt(clause.Token_Then)\
7879 end\
7880 printStat(clause.Body)\
7881 end\
7882 printt(stat.Token_End)\
7883 elseif stat.Type == 'CallExprStat' then\
7884 printExpr(stat.Expression)\
7885 elseif stat.Type == 'AssignmentStat' then\
7886 for index, ex in pairs(stat.Lhs) do\
7887 printExpr(ex)\
7888 local sep = stat.Token_LhsSeparatorList[index]\
7889 if sep then\
7890 printt(sep)\
7891 end\
7892 end\
7893 printt(stat.Token_Equals)\
7894 for index, ex in pairs(stat.Rhs) do\
7895 printExpr(ex)\
7896 local sep = stat.Token_RhsSeparatorList[index]\
7897 if sep then\
7898 printt(sep)\
7899 end\
7900 end\
7901 else\
7902 assert(false, \"unreachable\")\
7903 end\
7904 end\
7905\
7906 printStat(ast)\
7907end\
7908\
7909-- Adds / removes whitespace in an AST to put it into a \"standard formatting\"\
7910local function FormatAst(ast)\
7911 local formatStat, formatExpr;\
7912\
7913 local currentIndent = 0\
7914\
7915 local function applyIndent(token)\
7916 local indentString = '\\n'..('\\t'):rep(currentIndent)\
7917 if token.LeadingWhite == '' or (token.LeadingWhite:sub(-#indentString, -1) ~= indentString) then\
7918 -- Trim existing trailing whitespace on LeadingWhite\
7919 -- Trim trailing tabs and spaces, and up to one newline\
7920 token.LeadingWhite = token.LeadingWhite:gsub(\"\\n?[\\t ]*$\", \"\")\
7921 token.LeadingWhite = token.LeadingWhite..indentString\
7922 end\
7923 end\
7924\
7925 local function indent()\
7926 currentIndent = currentIndent + 1\
7927 end\
7928\
7929 local function undent()\
7930 currentIndent = currentIndent - 1\
7931 assert(currentIndent >= 0, \"Undented too far\")\
7932 end\
7933\
7934 local function leadingChar(tk)\
7935 if #tk.LeadingWhite > 0 then\
7936 return tk.LeadingWhite:sub(1,1)\
7937 else\
7938 return tk.Source:sub(1,1)\
7939 end\
7940 end\
7941\
7942 local function padToken(tk)\
7943 if not WhiteChars[leadingChar(tk)] then\
7944 tk.LeadingWhite = ' '..tk.LeadingWhite\
7945 end\
7946 end\
7947\
7948 local function padExpr(expr)\
7949 padToken(expr:GetFirstToken())\
7950 end\
7951\
7952 local function formatBody(_, bodyStat, closeToken)\
7953 indent()\
7954 formatStat(bodyStat)\
7955 undent()\
7956 applyIndent(closeToken)\
7957 end\
7958\
7959 formatExpr = function(expr)\
7960 if expr.Type == 'BinopExpr' then\
7961 formatExpr(expr.Lhs)\
7962 formatExpr(expr.Rhs)\
7963 if expr.Token_Op.Source == '..' then\
7964 -- No padding on ..\
7965 else\
7966 padExpr(expr.Rhs)\
7967 padToken(expr.Token_Op)\
7968 end\
7969 elseif expr.Type == 'UnopExpr' then\
7970 formatExpr(expr.Rhs)\
7971 --(expr.Token_Op)\
7972 elseif expr.Type == 'NumberLiteral' or expr.Type == 'StringLiteral' or\
7973 expr.Type == 'NilLiteral' or expr.Type == 'BooleanLiteral' or\
7974 expr.Type == 'VargLiteral'\
7975 then\
7976 -- Nothing to do\
7977 --(expr.Token)\
7978 elseif expr.Type == 'FieldExpr' then\
7979 formatExpr(expr.Base)\
7980 --(expr.Token_Dot)\
7981 --(expr.Field)\
7982 elseif expr.Type == 'IndexExpr' then\
7983 formatExpr(expr.Base)\
7984 formatExpr(expr.Index)\
7985 --(expr.Token_OpenBracket)\
7986 --(expr.Token_CloseBracket)\
7987 elseif expr.Type == 'MethodExpr' or expr.Type == 'CallExpr' then\
7988 formatExpr(expr.Base)\
7989 if expr.FunctionArguments.CallType == 'StringCall' then\
7990 --(expr.FunctionArguments.Token)\
7991 elseif expr.FunctionArguments.CallType == 'ArgCall' then\
7992 --(expr.FunctionArguments.Token_OpenParen)\
7993 for index, argExpr in pairs(expr.FunctionArguments.ArgList) do\
7994 formatExpr(argExpr)\
7995 if index > 1 then\
7996 padExpr(argExpr)\
7997 end\
7998 end\
7999 --(expr.FunctionArguments.Token_CloseParen)\
8000 elseif expr.FunctionArguments.CallType == 'TableCall' then\
8001 formatExpr(expr.FunctionArguments.TableExpr)\
8002 end\
8003 elseif expr.Type == 'FunctionLiteral' then\
8004 --(expr.Token_Function)\
8005 --(expr.Token_OpenParen)\
8006 for index, arg in pairs(expr.ArgList) do\
8007 --(arg)\
8008 if index > 1 then\
8009 padToken(arg)\
8010 end\
8011 end\
8012 --(expr.Token_CloseParen)\
8013 formatBody(expr.Token_CloseParen, expr.Body, expr.Token_End)\
8014 elseif expr.Type == 'VariableExpr' then\
8015 --(expr.Token)\
8016 elseif expr.Type == 'ParenExpr' then\
8017 formatExpr(expr.Expression)\
8018 --(expr.Token_OpenParen)\
8019 --(expr.Token_CloseParen)\
8020 elseif expr.Type == 'TableLiteral' then\
8021 --(expr.Token_OpenBrace)\
8022 if #expr.EntryList == 0 then\
8023 -- Nothing to do\
8024 else\
8025 indent()\
8026 for _, entry in pairs(expr.EntryList) do\
8027 if entry.EntryType == 'Field' then\
8028 applyIndent(entry.Field)\
8029 padToken(entry.Token_Equals)\
8030 formatExpr(entry.Value)\
8031 padExpr(entry.Value)\
8032 elseif entry.EntryType == 'Index' then\
8033 applyIndent(entry.Token_OpenBracket)\
8034 formatExpr(entry.Index)\
8035 --(entry.Token_CloseBracket)\
8036 padToken(entry.Token_Equals)\
8037 formatExpr(entry.Value)\
8038 padExpr(entry.Value)\
8039 elseif entry.EntryType == 'Value' then\
8040 formatExpr(entry.Value)\
8041 applyIndent(entry.Value:GetFirstToken())\
8042 else\
8043 assert(false, \"unreachable\")\
8044 end\
8045 end\
8046 undent()\
8047 applyIndent(expr.Token_CloseBrace)\
8048 end\
8049 --(expr.Token_CloseBrace)\
8050 else\
8051 assert(false, \"unreachable, type: \"..expr.Type..\":\"..FormatTable(expr))\
8052 end\
8053 end\
8054\
8055 formatStat = function(stat)\
8056 if stat.Type == 'StatList' then\
8057 for _, stat in pairs(stat.StatementList) do\
8058 formatStat(stat)\
8059 applyIndent(stat:GetFirstToken())\
8060 end\
8061\
8062 elseif stat.Type == 'BreakStat' then\
8063 --(stat.Token_Break)\
8064\
8065 elseif stat.Type == 'ReturnStat' then\
8066 --(stat.Token_Return)\
8067 for _, expr in pairs(stat.ExprList) do\
8068 formatExpr(expr)\
8069 padExpr(expr)\
8070 end\
8071 elseif stat.Type == 'LocalVarStat' then\
8072 --(stat.Token_Local)\
8073 for _, var in pairs(stat.VarList) do\
8074 padToken(var)\
8075 end\
8076 if stat.Token_Equals then\
8077 padToken(stat.Token_Equals)\
8078 for _, expr in pairs(stat.ExprList) do\
8079 formatExpr(expr)\
8080 padExpr(expr)\
8081 end\
8082 end\
8083 elseif stat.Type == 'LocalFunctionStat' then\
8084 --(stat.Token_Local)\
8085 padToken(stat.FunctionStat.Token_Function)\
8086 padToken(stat.FunctionStat.NameChain[1])\
8087 --(stat.FunctionStat.Token_OpenParen)\
8088 for index, arg in pairs(stat.FunctionStat.ArgList) do\
8089 if index > 1 then\
8090 padToken(arg)\
8091 end\
8092 end\
8093 --(stat.FunctionStat.Token_CloseParen)\
8094 formatBody(stat.FunctionStat.Token_CloseParen, stat.FunctionStat.Body, stat.FunctionStat.Token_End)\
8095 elseif stat.Type == 'FunctionStat' then\
8096 --(stat.Token_Function)\
8097 for index, part in pairs(stat.NameChain) do\
8098 if index == 1 then\
8099 padToken(part)\
8100 end\
8101 end\
8102 --(stat.Token_OpenParen)\
8103 for index, arg in pairs(stat.ArgList) do\
8104 if index > 1 then\
8105 padToken(arg)\
8106 end\
8107 end\
8108 --(stat.Token_CloseParen)\
8109 formatBody(stat.Token_CloseParen, stat.Body, stat.Token_End)\
8110 elseif stat.Type == 'RepeatStat' then\
8111 --(stat.Token_Repeat)\
8112 formatBody(stat.Token_Repeat, stat.Body, stat.Token_Until)\
8113 formatExpr(stat.Condition)\
8114 padExpr(stat.Condition)\
8115 elseif stat.Type == 'GenericForStat' then\
8116 --(stat.Token_For)\
8117 for _, var in pairs(stat.VarList) do\
8118 padToken(var)\
8119 end\
8120 padToken(stat.Token_In)\
8121 for _, expr in pairs(stat.GeneratorList) do\
8122 formatExpr(expr)\
8123 padExpr(expr)\
8124 end\
8125 padToken(stat.Token_Do)\
8126 formatBody(stat.Token_Do, stat.Body, stat.Token_End)\
8127 elseif stat.Type == 'NumericForStat' then\
8128 --(stat.Token_For)\
8129 for _, var in pairs(stat.VarList) do\
8130 padToken(var)\
8131 end\
8132 padToken(stat.Token_Equals)\
8133 for _, expr in pairs(stat.RangeList) do\
8134 formatExpr(expr)\
8135 padExpr(expr)\
8136 end\
8137 padToken(stat.Token_Do)\
8138 formatBody(stat.Token_Do, stat.Body, stat.Token_End)\
8139 elseif stat.Type == 'WhileStat' then\
8140 --(stat.Token_While)\
8141 formatExpr(stat.Condition)\
8142 padExpr(stat.Condition)\
8143 padToken(stat.Token_Do)\
8144 formatBody(stat.Token_Do, stat.Body, stat.Token_End)\
8145 elseif stat.Type == 'DoStat' then\
8146 --(stat.Token_Do)\
8147 formatBody(stat.Token_Do, stat.Body, stat.Token_End)\
8148 elseif stat.Type == 'IfStat' then\
8149 --(stat.Token_If)\
8150 formatExpr(stat.Condition)\
8151 padExpr(stat.Condition)\
8152 padToken(stat.Token_Then)\
8153 --\
8154 local lastBodyOpen = stat.Token_Then\
8155 local lastBody = stat.Body\
8156 --\
8157 for _, clause in pairs(stat.ElseClauseList) do\
8158 formatBody(lastBodyOpen, lastBody, clause.Token)\
8159 lastBodyOpen = clause.Token\
8160 --\
8161 if clause.Condition then\
8162 formatExpr(clause.Condition)\
8163 padExpr(clause.Condition)\
8164 padToken(clause.Token_Then)\
8165 lastBodyOpen = clause.Token_Then\
8166 end\
8167 lastBody = clause.Body\
8168 end\
8169 --\
8170 formatBody(lastBodyOpen, lastBody, stat.Token_End)\
8171\
8172 elseif stat.Type == 'CallExprStat' then\
8173 formatExpr(stat.Expression)\
8174 elseif stat.Type == 'AssignmentStat' then\
8175 for index, ex in pairs(stat.Lhs) do\
8176 formatExpr(ex)\
8177 if index > 1 then\
8178 padExpr(ex)\
8179 end\
8180 end\
8181 padToken(stat.Token_Equals)\
8182 for _, ex in pairs(stat.Rhs) do\
8183 formatExpr(ex)\
8184 padExpr(ex)\
8185 end\
8186 else\
8187 assert(false, \"unreachable\")\
8188 end\
8189 end\
8190\
8191 formatStat(ast)\
8192end\
8193\
8194-- Strips as much whitespace off of tokens in an AST as possible without causing problems\
8195local function StripAst(ast)\
8196 local stripStat, stripExpr;\
8197\
8198 local function stript(token)\
8199 token.LeadingWhite = ''\
8200 end\
8201\
8202 -- Make to adjacent tokens as close as possible\
8203 local function joint(tokenA, tokenB)\
8204 -- Strip the second token's whitespace\
8205 stript(tokenB)\
8206\
8207 -- Get the trailing A <-> leading B character pair\
8208 local lastCh = tokenA.Source:sub(-1, -1)\
8209 local firstCh = tokenB.Source:sub(1, 1)\
8210\
8211 -- Cases to consider:\
8212 -- Touching minus signs -> comment: `- -42` -> `--42' is invalid\
8213 -- Touching words: `a b` -> `ab` is invalid\
8214 -- Touching digits: `2 3`, can't occurr in the Lua syntax as number literals aren't a primary expression\
8215 -- Abiguous syntax: `f(x)\\n(x)()` is already disallowed, we can't cause a problem by removing newlines\
8216\
8217 -- Figure out what separation is needed\
8218 if\
8219 (lastCh == '-' and firstCh == '-') or\
8220 (AllIdentChars[lastCh] and AllIdentChars[firstCh])\
8221 then\
8222 tokenB.LeadingWhite = ' ' -- Use a separator\
8223 else\
8224 tokenB.LeadingWhite = '' -- Don't use a separator\
8225 end\
8226 end\
8227\
8228 -- Join up a statement body and it's opening / closing tokens\
8229 local function bodyjoint(open, body, close)\
8230 stripStat(body)\
8231 stript(close)\
8232 local bodyFirst = body:GetFirstToken()\
8233 local bodyLast = body:GetLastToken()\
8234 if bodyFirst then\
8235 -- Body is non-empty, join body to open / close\
8236 joint(open, bodyFirst)\
8237 joint(bodyLast, close)\
8238 else\
8239 -- Body is empty, just join open and close token together\
8240 joint(open, close)\
8241 end\
8242 end\
8243\
8244 stripExpr = function(expr)\
8245 if expr.Type == 'BinopExpr' then\
8246 stripExpr(expr.Lhs)\
8247 stript(expr.Token_Op)\
8248 stripExpr(expr.Rhs)\
8249 -- Handle the `a - -b` -/-> `a--b` case which would otherwise incorrectly generate a comment\
8250 -- Also handles operators \"or\" / \"and\" which definitely need joining logic in a bunch of cases\
8251 joint(expr.Token_Op, expr.Rhs:GetFirstToken())\
8252 joint(expr.Lhs:GetLastToken(), expr.Token_Op)\
8253 elseif expr.Type == 'UnopExpr' then\
8254 stript(expr.Token_Op)\
8255 stripExpr(expr.Rhs)\
8256 -- Handle the `- -b` -/-> `--b` case which would otherwise incorrectly generate a comment\
8257 joint(expr.Token_Op, expr.Rhs:GetFirstToken())\
8258 elseif expr.Type == 'NumberLiteral' or expr.Type == 'StringLiteral' or\
8259 expr.Type == 'NilLiteral' or expr.Type == 'BooleanLiteral' or\
8260 expr.Type == 'VargLiteral'\
8261 then\
8262 -- Just print the token\
8263 stript(expr.Token)\
8264 elseif expr.Type == 'FieldExpr' then\
8265 stripExpr(expr.Base)\
8266 stript(expr.Token_Dot)\
8267 stript(expr.Field)\
8268 elseif expr.Type == 'IndexExpr' then\
8269 stripExpr(expr.Base)\
8270 stript(expr.Token_OpenBracket)\
8271 stripExpr(expr.Index)\
8272 stript(expr.Token_CloseBracket)\
8273 elseif expr.Type == 'MethodExpr' or expr.Type == 'CallExpr' then\
8274 stripExpr(expr.Base)\
8275 if expr.Type == 'MethodExpr' then\
8276 stript(expr.Token_Colon)\
8277 stript(expr.Method)\
8278 end\
8279 if expr.FunctionArguments.CallType == 'StringCall' then\
8280 stript(expr.FunctionArguments.Token)\
8281 elseif expr.FunctionArguments.CallType == 'ArgCall' then\
8282 stript(expr.FunctionArguments.Token_OpenParen)\
8283 for index, argExpr in pairs(expr.FunctionArguments.ArgList) do\
8284 stripExpr(argExpr)\
8285 local sep = expr.FunctionArguments.Token_CommaList[index]\
8286 if sep then\
8287 stript(sep)\
8288 end\
8289 end\
8290 stript(expr.FunctionArguments.Token_CloseParen)\
8291 elseif expr.FunctionArguments.CallType == 'TableCall' then\
8292 stripExpr(expr.FunctionArguments.TableExpr)\
8293 end\
8294 elseif expr.Type == 'FunctionLiteral' then\
8295 stript(expr.Token_Function)\
8296 stript(expr.Token_OpenParen)\
8297 for index, arg in pairs(expr.ArgList) do\
8298 stript(arg)\
8299 local comma = expr.Token_ArgCommaList[index]\
8300 if comma then\
8301 stript(comma)\
8302 end\
8303 end\
8304 stript(expr.Token_CloseParen)\
8305 bodyjoint(expr.Token_CloseParen, expr.Body, expr.Token_End)\
8306 elseif expr.Type == 'VariableExpr' then\
8307 stript(expr.Token)\
8308 elseif expr.Type == 'ParenExpr' then\
8309 stript(expr.Token_OpenParen)\
8310 stripExpr(expr.Expression)\
8311 stript(expr.Token_CloseParen)\
8312 elseif expr.Type == 'TableLiteral' then\
8313 stript(expr.Token_OpenBrace)\
8314 for index, entry in pairs(expr.EntryList) do\
8315 if entry.EntryType == 'Field' then\
8316 stript(entry.Field)\
8317 stript(entry.Token_Equals)\
8318 stripExpr(entry.Value)\
8319 elseif entry.EntryType == 'Index' then\
8320 stript(entry.Token_OpenBracket)\
8321 stripExpr(entry.Index)\
8322 stript(entry.Token_CloseBracket)\
8323 stript(entry.Token_Equals)\
8324 stripExpr(entry.Value)\
8325 elseif entry.EntryType == 'Value' then\
8326 stripExpr(entry.Value)\
8327 else\
8328 assert(false, \"unreachable\")\
8329 end\
8330 local sep = expr.Token_SeparatorList[index]\
8331 if sep then\
8332 stript(sep)\
8333 end\
8334 end\
8335 stript(expr.Token_CloseBrace)\
8336 else\
8337 assert(false, \"unreachable, type: \"..expr.Type..\":\"..FormatTable(expr))\
8338 end\
8339 end\
8340\
8341 stripStat = function(stat)\
8342 if stat.Type == 'StatList' then\
8343 -- Strip all surrounding whitespace on statement lists along with separating whitespace\
8344 for i = 1, #stat.StatementList do\
8345 local chStat = stat.StatementList[i]\
8346\
8347 -- Strip the statement and it's whitespace\
8348 stripStat(chStat)\
8349 stript(chStat:GetFirstToken())\
8350\
8351 -- If there was a last statement, join them appropriately\
8352 local lastChStat = stat.StatementList[i-1]\
8353 if lastChStat then\
8354 -- See if we can remove a semi-colon, the only case where we can't is if\
8355 -- this and the last statement have a `);(` pair, where removing the semi-colon\
8356 -- would introduce ambiguous syntax.\
8357 if stat.SemicolonList[i-1] and\
8358 (lastChStat:GetLastToken().Source ~= ')' or chStat:GetFirstToken().Source ~= ')')\
8359 then\
8360 stat.SemicolonList[i-1] = nil\
8361 end\
8362\
8363 -- If there isn't a semi-colon, we should safely join the two statements\
8364 -- (If there is one, then no whitespace leading chStat is always okay)\
8365 if not stat.SemicolonList[i-1] then\
8366 joint(lastChStat:GetLastToken(), chStat:GetFirstToken())\
8367 end\
8368 end\
8369 end\
8370\
8371 -- A semi-colon is never needed on the last stat in a statlist:\
8372 stat.SemicolonList[#stat.StatementList] = nil\
8373\
8374 -- The leading whitespace on the statlist should be stripped\
8375 if #stat.StatementList > 0 then\
8376 stript(stat.StatementList[1]:GetFirstToken())\
8377 end\
8378\
8379 elseif stat.Type == 'BreakStat' then\
8380 stript(stat.Token_Break)\
8381\
8382 elseif stat.Type == 'ReturnStat' then\
8383 stript(stat.Token_Return)\
8384 for index, expr in pairs(stat.ExprList) do\
8385 stripExpr(expr)\
8386 if stat.Token_CommaList[index] then\
8387 stript(stat.Token_CommaList[index])\
8388 end\
8389 end\
8390 if #stat.ExprList > 0 then\
8391 joint(stat.Token_Return, stat.ExprList[1]:GetFirstToken())\
8392 end\
8393 elseif stat.Type == 'LocalVarStat' then\
8394 stript(stat.Token_Local)\
8395 for index, var in pairs(stat.VarList) do\
8396 if index == 1 then\
8397 joint(stat.Token_Local, var)\
8398 else\
8399 stript(var)\
8400 end\
8401 local comma = stat.Token_VarCommaList[index]\
8402 if comma then\
8403 stript(comma)\
8404 end\
8405 end\
8406 if stat.Token_Equals then\
8407 stript(stat.Token_Equals)\
8408 for index, expr in pairs(stat.ExprList) do\
8409 stripExpr(expr)\
8410 local comma = stat.Token_ExprCommaList[index]\
8411 if comma then\
8412 stript(comma)\
8413 end\
8414 end\
8415 end\
8416 elseif stat.Type == 'LocalFunctionStat' then\
8417 stript(stat.Token_Local)\
8418 joint(stat.Token_Local, stat.FunctionStat.Token_Function)\
8419 joint(stat.FunctionStat.Token_Function, stat.FunctionStat.NameChain[1])\
8420 joint(stat.FunctionStat.NameChain[1], stat.FunctionStat.Token_OpenParen)\
8421 for index, arg in pairs(stat.FunctionStat.ArgList) do\
8422 stript(arg)\
8423 local comma = stat.FunctionStat.Token_ArgCommaList[index]\
8424 if comma then\
8425 stript(comma)\
8426 end\
8427 end\
8428 stript(stat.FunctionStat.Token_CloseParen)\
8429 bodyjoint(stat.FunctionStat.Token_CloseParen, stat.FunctionStat.Body, stat.FunctionStat.Token_End)\
8430 elseif stat.Type == 'FunctionStat' then\
8431 stript(stat.Token_Function)\
8432 for index, part in pairs(stat.NameChain) do\
8433 if index == 1 then\
8434 joint(stat.Token_Function, part)\
8435 else\
8436 stript(part)\
8437 end\
8438 local sep = stat.Token_NameChainSeparator[index]\
8439 if sep then\
8440 stript(sep)\
8441 end\
8442 end\
8443 stript(stat.Token_OpenParen)\
8444 for index, arg in pairs(stat.ArgList) do\
8445 stript(arg)\
8446 local comma = stat.Token_ArgCommaList[index]\
8447 if comma then\
8448 stript(comma)\
8449 end\
8450 end\
8451 stript(stat.Token_CloseParen)\
8452 bodyjoint(stat.Token_CloseParen, stat.Body, stat.Token_End)\
8453 elseif stat.Type == 'RepeatStat' then\
8454 stript(stat.Token_Repeat)\
8455 bodyjoint(stat.Token_Repeat, stat.Body, stat.Token_Until)\
8456 stripExpr(stat.Condition)\
8457 joint(stat.Token_Until, stat.Condition:GetFirstToken())\
8458 elseif stat.Type == 'GenericForStat' then\
8459 stript(stat.Token_For)\
8460 for index, var in pairs(stat.VarList) do\
8461 if index == 1 then\
8462 joint(stat.Token_For, var)\
8463 else\
8464 stript(var)\
8465 end\
8466 local sep = stat.Token_VarCommaList[index]\
8467 if sep then\
8468 stript(sep)\
8469 end\
8470 end\
8471 joint(stat.VarList[#stat.VarList], stat.Token_In)\
8472 for index, expr in pairs(stat.GeneratorList) do\
8473 stripExpr(expr)\
8474 if index == 1 then\
8475 joint(stat.Token_In, expr:GetFirstToken())\
8476 end\
8477 local sep = stat.Token_GeneratorCommaList[index]\
8478 if sep then\
8479 stript(sep)\
8480 end\
8481 end\
8482 joint(stat.GeneratorList[#stat.GeneratorList]:GetLastToken(), stat.Token_Do)\
8483 bodyjoint(stat.Token_Do, stat.Body, stat.Token_End)\
8484 elseif stat.Type == 'NumericForStat' then\
8485 stript(stat.Token_For)\
8486 for index, var in pairs(stat.VarList) do\
8487 if index == 1 then\
8488 joint(stat.Token_For, var)\
8489 else\
8490 stript(var)\
8491 end\
8492 local sep = stat.Token_VarCommaList[index]\
8493 if sep then\
8494 stript(sep)\
8495 end\
8496 end\
8497 joint(stat.VarList[#stat.VarList], stat.Token_Equals)\
8498 for index, expr in pairs(stat.RangeList) do\
8499 stripExpr(expr)\
8500 if index == 1 then\
8501 joint(stat.Token_Equals, expr:GetFirstToken())\
8502 end\
8503 local sep = stat.Token_RangeCommaList[index]\
8504 if sep then\
8505 stript(sep)\
8506 end\
8507 end\
8508 joint(stat.RangeList[#stat.RangeList]:GetLastToken(), stat.Token_Do)\
8509 bodyjoint(stat.Token_Do, stat.Body, stat.Token_End)\
8510 elseif stat.Type == 'WhileStat' then\
8511 stript(stat.Token_While)\
8512 stripExpr(stat.Condition)\
8513 stript(stat.Token_Do)\
8514 joint(stat.Token_While, stat.Condition:GetFirstToken())\
8515 joint(stat.Condition:GetLastToken(), stat.Token_Do)\
8516 bodyjoint(stat.Token_Do, stat.Body, stat.Token_End)\
8517 elseif stat.Type == 'DoStat' then\
8518 stript(stat.Token_Do)\
8519 stript(stat.Token_End)\
8520 bodyjoint(stat.Token_Do, stat.Body, stat.Token_End)\
8521 elseif stat.Type == 'IfStat' then\
8522 stript(stat.Token_If)\
8523 stripExpr(stat.Condition)\
8524 joint(stat.Token_If, stat.Condition:GetFirstToken())\
8525 joint(stat.Condition:GetLastToken(), stat.Token_Then)\
8526 --\
8527 local lastBodyOpen = stat.Token_Then\
8528 local lastBody = stat.Body\
8529 --\
8530 for _, clause in pairs(stat.ElseClauseList) do\
8531 bodyjoint(lastBodyOpen, lastBody, clause.Token)\
8532 lastBodyOpen = clause.Token\
8533 --\
8534 if clause.Condition then\
8535 stripExpr(clause.Condition)\
8536 joint(clause.Token, clause.Condition:GetFirstToken())\
8537 joint(clause.Condition:GetLastToken(), clause.Token_Then)\
8538 lastBodyOpen = clause.Token_Then\
8539 end\
8540 stripStat(clause.Body)\
8541 lastBody = clause.Body\
8542 end\
8543 --\
8544 bodyjoint(lastBodyOpen, lastBody, stat.Token_End)\
8545\
8546 elseif stat.Type == 'CallExprStat' then\
8547 stripExpr(stat.Expression)\
8548 elseif stat.Type == 'AssignmentStat' then\
8549 for index, ex in pairs(stat.Lhs) do\
8550 stripExpr(ex)\
8551 local sep = stat.Token_LhsSeparatorList[index]\
8552 if sep then\
8553 stript(sep)\
8554 end\
8555 end\
8556 stript(stat.Token_Equals)\
8557 for index, ex in pairs(stat.Rhs) do\
8558 stripExpr(ex)\
8559 local sep = stat.Token_RhsSeparatorList[index]\
8560 if sep then\
8561 stript(sep)\
8562 end\
8563 end\
8564 else\
8565 assert(false, \"unreachable\")\
8566 end\
8567 end\
8568\
8569 stripStat(ast)\
8570end\
8571\
8572local VarDigits = {}\
8573for i = ('a'):byte(), ('z'):byte() do table.insert(VarDigits, string.char(i)) end\
8574for i = ('A'):byte(), ('Z'):byte() do table.insert(VarDigits, string.char(i)) end\
8575for i = ('0'):byte(), ('9'):byte() do table.insert(VarDigits, string.char(i)) end\
8576table.insert(VarDigits, '_')\
8577local VarStartDigits = {}\
8578for i = ('a'):byte(), ('z'):byte() do table.insert(VarStartDigits, string.char(i)) end\
8579for i = ('A'):byte(), ('Z'):byte() do table.insert(VarStartDigits, string.char(i)) end\
8580local function indexToVarName(index)\
8581 local id = ''\
8582 local d = index % #VarStartDigits\
8583 index = (index - d) / #VarStartDigits\
8584 id = id..VarStartDigits[d+1]\
8585 while index > 0 do\
8586 local d = index % #VarDigits\
8587 index = (index - d) / #VarDigits\
8588 id = id..VarDigits[d+1]\
8589 end\
8590 return id\
8591end\
8592local function MinifyVariables(globalScope, rootScope)\
8593 -- externalGlobals is a set of global variables that have not been assigned to, that is\
8594 -- global variables defined \"externally to the script\". We are not going to be renaming\
8595 -- those, and we have to make sure that we don't collide with them when renaming\
8596 -- things so we keep track of them in this set.\
8597 local externalGlobals = {}\
8598\
8599 -- First we want to rename all of the variables to unique temoraries, so that we can\
8600 -- easily use the scope::GetVar function to check whether renames are valid.\
8601 local temporaryIndex = 0\
8602 for _, var in pairs(globalScope) do\
8603 if var.AssignedTo then\
8604 var:Rename('_TMP_'..temporaryIndex..'_')\
8605 temporaryIndex = temporaryIndex + 1\
8606 else\
8607 -- Not assigned to, external global\
8608 externalGlobals[var.Name] = true\
8609 end\
8610 end\
8611\
8612 -- Now we go through renaming, first do globals, we probably want them\
8613 -- to have shorter names in general.\
8614 -- TODO: Rename all vars based on frequency patterns, giving variables\
8615 -- used more shorter names.\
8616 local nextFreeNameIndex = 0\
8617 for _, var in pairs(globalScope) do\
8618 if var.AssignedTo then\
8619 local varName\
8620 repeat\
8621 varName = indexToVarName(nextFreeNameIndex)\
8622 nextFreeNameIndex = nextFreeNameIndex + 1\
8623 until not Keywords[varName] and not externalGlobals[varName]\
8624 var:Rename(varName)\
8625 end\
8626 end\
8627\
8628 -- Now rename all local vars\
8629 rootScope.FirstFreeName = nextFreeNameIndex\
8630 local function doRenameScope(scope)\
8631 for _, var in pairs(scope.VariableList) do\
8632 local varName\
8633 repeat\
8634 varName = indexToVarName(scope.FirstFreeName)\
8635 scope.FirstFreeName = scope.FirstFreeName + 1\
8636 until not Keywords[varName] and not externalGlobals[varName]\
8637 var:Rename(varName)\
8638 end\
8639 for _, childScope in pairs(scope.ChildScopeList) do\
8640 childScope.FirstFreeName = scope.FirstFreeName\
8641 doRenameScope(childScope)\
8642 end\
8643 end\
8644 doRenameScope(rootScope)\
8645end\
8646\
8647local function BeautifyVariables(globalScope, rootScope)\
8648 local localNumber = 1\
8649 local globalNumber = 1\
8650\
8651 local function setVarName(var, name)\
8652 var.Name = name\
8653 for _, setter in pairs(var.RenameList) do\
8654 setter(name)\
8655 end\
8656 end\
8657\
8658 for _, var in pairs(globalScope) do\
8659 if var.AssignedTo then\
8660 setVarName(var, 'G_'..globalNumber)\
8661 globalNumber = globalNumber + 1\
8662 end\
8663 end\
8664\
8665 local function modify(scope)\
8666 for _, var in pairs(scope.VariableList) do\
8667 local name = 'L_'..localNumber..'_'\
8668 if var.Info.Type == 'Argument' then\
8669 name = name..'arg'..var.Info.Index\
8670 elseif var.Info.Type == 'LocalFunction' then\
8671 name = name..'func'\
8672 elseif var.Info.Type == 'ForRange' then\
8673 name = name..'forvar'..var.Info.Index\
8674 end\
8675 setVarName(var, name)\
8676 localNumber = localNumber + 1\
8677 end\
8678 for _, scope in pairs(scope.ChildScopeList) do\
8679 modify(scope)\
8680 end\
8681 end\
8682 modify(rootScope)\
8683end\
8684\
8685local function usageError()\
8686 printError(\
8687 \"\\nusage: minify <file> or unminify <file>\\n\" ..\
8688 \" The modified code will be printed to the stdout, pipe it to a file, the\\n\" ..\
8689 \" lua interpreter, or something else as desired EG:\\n\\n\" ..\
8690 \" lua minify.lua minify input.lua > output.lua\\n\\n\" ..\
8691 \" * minify will minify the code in the file.\\n\" ..\
8692 \" * unminify will beautify the code and replace the variable names with easily\\n\" ..\
8693 \" find-replacable ones to aide in reverse engineering minified code.\\n\")\
8694end\
8695\
8696local args = {...}\
8697if #args ~= 2 then\
8698 usageError()\
8699 error('Invalid arguments')\
8700end\
8701\
8702local sourceFile = io.open(args[2], 'r')\
8703if not sourceFile then\
8704 error(\"Could not open the input file `\" .. args[2] .. \"`\")\
8705end\
8706\
8707local data = sourceFile:read('*all')\
8708sourceFile:close()\
8709\
8710local ast = CreateLuaParser(data)\
8711local global_scope, root_scope = AddVariableInfo(ast)\
8712local out = { }\
8713\
8714if args[1] == 'minify' then\
8715 MinifyVariables(global_scope, root_scope)\
8716 StripAst(ast)\
8717elseif args[1] == 'unminify' then\
8718 BeautifyVariables(global_scope, root_scope)\
8719 FormatAst(ast)\
8720else\
8721 usageError()\
8722end\
8723\
8724PrintAst(ast, out)\
8725\
8726local destFile = io.open(args[2], 'w')\
8727if not destFile then\
8728 error(\"Could not open the output file `\" .. args[3] .. \"`\")\
8729end\
8730destFile:write(table.concat(out))\
8731destFile:close()",
8732 [ "common/Appstore.lua" ] = "local Ansi = require('opus.ansi')\
8733local SHA = require('opus.crypto.sha2')\
8734local UI = require('opus.ui')\
8735local Util = require('opus.util')\
8736\
8737local fs = _G.fs\
8738local http = _G.http\
8739local multishell = _ENV.multishell\
8740local os = _G.os\
8741local shell = _ENV.shell\
8742local colors = _G.colors\
8743\
8744local REGISTRY_DIR = 'usr/.registry'\
8745\
8746-- FIX SOMEDAY\
8747local function registerApp(app, key)\
8748 app.key = SHA.compute(key)\
8749 Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app)\
8750 os.queueEvent('os_register_app')\
8751end\
8752\
8753local function unregisterApp(key)\
8754 local filename = fs.combine(REGISTRY_DIR, SHA.compute(key))\
8755 if fs.exists(filename) then\
8756 fs.delete(filename)\
8757 os.queueEvent('os_register_app')\
8758 end\
8759end\
8760\
8761multishell.setTitle(multishell.getCurrent(), 'App Store')\
8762UI:configure('Appstore', ...)\
8763\
8764local APP_DIR = 'usr/apps'\
8765\
8766local source = {\
8767 text = \"STD Default\",\
8768 event = 'source',\
8769 url = \"https://github.com/LDDestroier/STD-GUI/raw/master/list.lua\",\
8770}\
8771\
8772shell.setDir(APP_DIR)\
8773\
8774local function downloadApp(app)\
8775 local h\
8776\
8777 if type(app.url) == \"table\" then\
8778 h = contextualGet(app.url[1])\
8779 else\
8780 h = http.get(app.url)\
8781 end\
8782\
8783 if h then\
8784 local contents = h.readAll()\
8785 h:close()\
8786 return contents\
8787 end\
8788end\
8789\
8790local function runApp(app, checkExists, ...)\
8791 local env = shell.makeEnv(_ENV)\
8792 local path, fn\
8793 local args = { ... }\
8794\
8795 if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then\
8796 path = fs.combine(APP_DIR, app.name)\
8797 else\
8798 local program = downloadApp(app)\
8799\
8800 fn = function()\
8801\
8802 if not program then\
8803 error('Failed to download')\
8804 end\
8805\
8806 fn = _G.loadstring(program, app.name)\
8807\
8808 if not fn then\
8809 error('Failed to download')\
8810 end\
8811\
8812 _G.setfenv(fn, env)\
8813 fn(table.unpack(args))\
8814 end\
8815 end\
8816\
8817 multishell.openTab(_ENV, {\
8818 title = app.name,\
8819 path = path,\
8820 fn = fn,\
8821 focused = true,\
8822 })\
8823\
8824 return true, 'Running program'\
8825end\
8826\
8827local installApp = function(app)\
8828\
8829 local program = downloadApp(app)\
8830 if not program then\
8831 return false, \"Failed to download\"\
8832 end\
8833\
8834 local fullPath = fs.combine(APP_DIR, app.name)\
8835 Util.writeFile(fullPath, program)\
8836 return true, 'Installed as ' .. fullPath\
8837end\
8838\
8839local viewApp = function(app)\
8840\
8841 local program = downloadApp(app)\
8842 if not program then\
8843 return false, \"Failed to download\"\
8844 end\
8845\
8846 Util.writeFile('/.source', program)\
8847 shell.openForegroundTab('edit /.source')\
8848 fs.delete('/.source')\
8849 return true\
8850end\
8851\
8852local getSourceListing = function()\
8853 local contents = http.get(source.url)\
8854 if contents then\
8855\
8856 local fn = _G.loadstring(contents.readAll(), source.text)\
8857 contents.close()\
8858\
8859 local env = { std = { } }\
8860 setmetatable(env, { __index = _G })\
8861 _G.setfenv(fn, env)\
8862 fn()\
8863\
8864 if env.contextualGet then\
8865 contextualGet = env.contextualGet\
8866 end\
8867\
8868 source.storeURLs = env.std.storeURLs\
8869 source.storeCatagoryNames = env.std.storeCatagoryNames\
8870\
8871 if source.storeURLs and source.storeCatagoryNames then\
8872 for k,v in pairs(source.storeURLs) do\
8873 if source.generateName then\
8874 v.name = v.title:match('(%w+)')\
8875 if not v.name or #v.name == 0 then\
8876 v.name = tostring(k)\
8877 else\
8878 v.name = v.name:lower()\
8879 end\
8880 else\
8881 v.name = k\
8882 end\
8883 v.categoryName = source.storeCatagoryNames[v.catagory]\
8884 v.ltitle = v.title:lower()\
8885 end\
8886 end\
8887 end\
8888end\
8889\
8890getSourceListing()\
8891\
8892if not source.storeURLs then\
8893 error('Unable to download application list')\
8894end\
8895\
8896local buttons = { }\
8897for k,v in Util.spairs(source.storeCatagoryNames,\
8898 function(a, b) return a:lower() < b:lower() end) do\
8899\
8900 if v ~= 'Operating System' then\
8901 table.insert(buttons, {\
8902 text = v,\
8903 event = 'category',\
8904 index = k,\
8905 })\
8906 end\
8907end\
8908source.index, source.name = Util.first(source.storeCatagoryNames)\
8909\
8910local appPage = UI.Page {\
8911 menuBar = UI.MenuBar {\
8912 buttons = {\
8913 { text = '\\027', event = 'back' },\
8914 { text = 'Install', event = 'install' },\
8915 { text = 'Run', event = 'run' },\
8916 { text = 'View', event = 'view' },\
8917 { text = 'Remove', event = 'uninstall', name = 'removeButton' },\
8918 },\
8919 },\
8920 container = UI.Window {\
8921 x = 2, y = 3, ex = -2, ey = -3,\
8922 viewport = UI.Viewport(),\
8923 },\
8924 notification = UI.Notification(),\
8925 accelerators = {\
8926 [ 'control-q' ] = 'back',\
8927 backspace = 'back',\
8928 },\
8929}\
8930\
8931function appPage.container.viewport:draw()\
8932 local app = self.parent.parent.app\
8933 local str = string.format(\
8934 '%s \\nBy: %s \\nCategory: %s\\nFile name: %s\\n\\n%s',\
8935 Ansi.yellow .. app.title .. Ansi.reset,\
8936 app.creator,\
8937 app.categoryName, app.name,\
8938 Ansi.yellow .. app.description .. Ansi.reset)\
8939\
8940 self:clear()\
8941 self:print(str)\
8942\
8943 if appPage.notification.enabled then\
8944 appPage.notification:draw()\
8945 end\
8946end\
8947\
8948function appPage:enable(app)\
8949 self.source = source\
8950 self.app = app\
8951 UI.Page.enable(self)\
8952\
8953 self.container.viewport:setScrollPosition(0)\
8954 if fs.exists(fs.combine(APP_DIR, app.name)) then\
8955 self.menuBar.removeButton:enable('Remove')\
8956 else\
8957 self.menuBar.removeButton:disable('Remove')\
8958 end\
8959end\
8960\
8961function appPage:eventHandler(event)\
8962 if event.type == 'back' then\
8963 UI:setPreviousPage()\
8964\
8965 elseif event.type == 'run' then\
8966 self.notification:info('Running program', 3)\
8967 self:sync()\
8968 runApp(self.app, true)\
8969\
8970 elseif event.type == 'view' then\
8971 self.notification:info('Downloading program', 3)\
8972 self:sync()\
8973 viewApp(self.app)\
8974\
8975 elseif event.type == 'uninstall' then\
8976 if self.app.runOnly then\
8977 runApp(self.app, false, 'uninstall')\
8978 else\
8979 fs.delete(fs.combine(APP_DIR, self.app.name))\
8980 self.notification:success(\"Uninstalled \" .. self.app.name, 3)\
8981 self:focusFirst(self)\
8982 self.menuBar.removeButton:disable('Remove')\
8983 self.menuBar:draw()\
8984\
8985 unregisterApp(self.app.creator .. '.' .. self.app.name)\
8986 end\
8987\
8988 elseif event.type == 'install' then\
8989 self.notification:info(\"Installing\", 3)\
8990 self:sync()\
8991 local s, m\
8992 if self.app.runOnly then\
8993 s,m = runApp(self.app, false)\
8994 else\
8995 s,m = installApp(self.app)\
8996 end\
8997 if s then\
8998 self.notification:success(m, 3)\
8999\
9000 if not self.app.runOnly then\
9001 self.menuBar.removeButton:enable('Remove')\
9002 self.menuBar:draw()\
9003\
9004 local category = 'Apps'\
9005 if self.app.catagoryName == 'Game' then\
9006 category = 'Games'\
9007 end\
9008\
9009 registerApp({\
9010 run = fs.combine(APP_DIR, self.app.name),\
9011 title = self.app.title,\
9012 category = category,\
9013 icon = self.app.icon,\
9014 }, self.app.creator .. '.' .. self.app.name)\
9015 end\
9016 else\
9017 self.notification:error(m, 3)\
9018 end\
9019 else\
9020 return UI.Page.eventHandler(self, event)\
9021 end\
9022 return true\
9023end\
9024\
9025local categoryPage = UI.Page {\
9026 menuBar = UI.MenuBar {\
9027 buttons = {\
9028 { text = 'Category', name = 'categoryButton', dropdown = buttons },\
9029 },\
9030 },\
9031 grid = UI.ScrollingGrid {\
9032 y = 2, ey = -2,\
9033 columns = {\
9034 { heading = 'Title', key = 'title' },\
9035 },\
9036 sortColumn = 'title',\
9037 },\
9038 statusBar = UI.StatusBar(),\
9039 accelerators = {\
9040 l = 'lua',\
9041 [ 'control-q' ] = 'quit',\
9042 },\
9043 source = source,\
9044}\
9045\
9046function categoryPage:setCategory(name, index)\
9047 self.grid.values = { }\
9048 for _,v in pairs(source.storeURLs) do\
9049 if index == 0 or index == v.catagory then\
9050 table.insert(self.grid.values, v)\
9051 end\
9052 end\
9053 self.statusBar:setStatus(string.format('%s: %s', self.source.text or '', name))\
9054 self.grid:update()\
9055 self.grid:setIndex(1)\
9056end\
9057\
9058function categoryPage.grid:sortCompare(a, b)\
9059 return a.ltitle < b.ltitle\
9060end\
9061\
9062function categoryPage.grid:getRowTextColor(row, selected)\
9063 if fs.exists(fs.combine(APP_DIR, row.name)) then\
9064 return colors.orange\
9065 end\
9066 return UI.Grid:getRowTextColor(row, selected)\
9067end\
9068\
9069function categoryPage:eventHandler(event)\
9070 if event.type == 'grid_select' or event.type == 'select' then\
9071 UI:setPage(appPage, self.grid:getSelected())\
9072\
9073 elseif event.type == 'category' then\
9074 self:setCategory(event.button.text, event.button.index)\
9075 self:setFocus(self.grid)\
9076 self:draw()\
9077\
9078 elseif event.type == 'source' then\
9079 self:setFocus(self.grid)\
9080 self:setSource(event.button)\
9081 self:draw()\
9082\
9083 elseif event.type == 'quit' then\
9084 UI:quit()\
9085\
9086 else\
9087 return UI.Page.eventHandler(self, event)\
9088 end\
9089 return true\
9090end\
9091\
9092print(\"Retrieving catalog list\")\
9093categoryPage:setCategory(source.name, source.index)\
9094\
9095UI:setPage(categoryPage)\
9096UI:start()",
9097 [ "common/Turtles.lua" ] = "local Config = require('opus.config')\
9098local Event = require('opus.event')\
9099local itemDB = require('core.itemDB')\
9100local Socket = require('opus.socket')\
9101local UI = require('opus.ui')\
9102local Util = require('opus.util')\
9103\
9104local fs = _G.fs\
9105local multishell = _ENV.multishell\
9106local network = _G.network\
9107local os = _G.os\
9108\
9109UI:configure('Turtles', ...)\
9110\
9111local config = { }\
9112Config.load('Turtles', config)\
9113\
9114local options = {\
9115 turtle = { arg = 'i', type = 'number', value = config.id or -1,\
9116 desc = 'Turtle ID' },\
9117 tab = { arg = 's', type = 'string', value = config.tab or 'Sel',\
9118 desc = 'Selected tab to display' },\
9119 help = { arg = 'h', type = 'flag', value = false,\
9120 desc = 'Displays the options' },\
9121}\
9122\
9123local SCRIPTS_PATH = 'packages/common/etc/scripts'\
9124\
9125local socket, turtle, page\
9126\
9127page = UI.Page {\
9128 coords = UI.Window {\
9129 backgroundColor = 'black',\
9130 height = 3,\
9131 marginTop = 1, marginLeft = 1,\
9132 draw = function(self)\
9133 local t = turtle\
9134 self:clear()\
9135 if t then\
9136 self:setCursorPos(2, 2)\
9137 local ind = 'GPS'\
9138 if not t.point.gps then\
9139 ind = 'REL'\
9140 end\
9141 self:print(string.format('%s : %d,%d,%d',\
9142 ind, t.point.x, t.point.y, t.point.z))\
9143 end\
9144 end,\
9145 },\
9146 tabs = UI.Tabs {\
9147 x = 1, y = 4, ey = -2,\
9148 UI.Tab {\
9149 title = 'Run',\
9150 scripts = UI.ScrollingGrid {\
9151 backgroundColor = 'primary',\
9152 columns = {\
9153 { heading = '', key = 'label' },\
9154 },\
9155 disableHeader = true,\
9156 sortColumn = 'label',\
9157 autospace = true,\
9158 draw = function(self)\
9159 Util.clear(self.values)\
9160 local files = fs.list(SCRIPTS_PATH)\
9161 for _,path in pairs(files) do\
9162 table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })\
9163 end\
9164 self:update()\
9165 UI.ScrollingGrid.draw(self)\
9166 end,\
9167 eventHandler = function(self, event)\
9168 if event.type == 'grid_select' then\
9169 page:runScript(event.selected.label)\
9170 else\
9171 return UI.ScrollingGrid.eventHandler(self, event)\
9172 end\
9173 return true\
9174 end,\
9175 },\
9176 },\
9177 UI.Tab {\
9178 title = 'Select',\
9179 turtles = UI.ScrollingGrid {\
9180 backgroundColor = 'primary',\
9181 columns = {\
9182 { heading = 'label', key = 'label' },\
9183 { heading = 'Dist', key = 'distance' },\
9184 { heading = 'Status', key = 'status' },\
9185 { heading = 'Fuel', key = 'fuel' },\
9186 },\
9187 disableHeader = true,\
9188 sortColumn = 'label',\
9189 autospace = true,\
9190 getDisplayValues = function(_, row)\
9191 row = Util.shallowCopy(row)\
9192 if row.fuel then\
9193 row.fuel = Util.toBytes(row.fuel)\
9194 end\
9195 if row.distance then\
9196 row.distance = Util.round(row.distance, 1)\
9197 end\
9198 return row\
9199 end,\
9200 draw = function(self)\
9201 Util.clear(self.values)\
9202 for _,v in pairs(network) do\
9203 if v.fuel then\
9204 table.insert(self.values, v)\
9205 end\
9206 end\
9207 self:update()\
9208 UI.ScrollingGrid.draw(self)\
9209 end,\
9210 eventHandler = function(self, event)\
9211 if event.type == 'grid_select' then\
9212 turtle = event.selected\
9213 config.id = event.selected.id\
9214 Config.update('Turtles', config)\
9215 multishell.setTitle(multishell.getCurrent(), turtle.label)\
9216 if socket then\
9217 socket:close()\
9218 socket = nil\
9219 end\
9220 else\
9221 return UI.ScrollingGrid.eventHandler(self, event)\
9222 end\
9223 return true\
9224 end,\
9225 },\
9226 },\
9227 UI.Tab {\
9228 title = 'Inv',\
9229 inventory = UI.ScrollingGrid {\
9230 backgroundColor = 'primary',\
9231 columns = {\
9232 { heading = '', key = 'index', width = 2 },\
9233 { heading = '', key = 'count', width = 2 },\
9234 { heading = 'Inventory', key = 'key' },\
9235 },\
9236 disableHeader = true,\
9237 sortColumn = 'index',\
9238 getRowTextColor = function(self, row, selected)\
9239 if turtle and row.selected then\
9240 return 'yellow'\
9241 end\
9242 return UI.ScrollingGrid.getRowTextColor(self, row, selected)\
9243 end,\
9244 draw = function(self)\
9245 local t = turtle\
9246 Util.clear(self.values)\
9247 if t then\
9248 for k,v in pairs(t.inv or { }) do -- new method (less data)\
9249 local index, count = k:match('(%d+),(%d+)')\
9250 v = {\
9251 index = tonumber(index),\
9252 key = v,\
9253 count = tonumber(count),\
9254 }\
9255 table.insert(self.values, v)\
9256 end\
9257\
9258 for _,v in pairs(t.inventory or { }) do\
9259 if v.count > 0 then\
9260 table.insert(self.values, v)\
9261 end\
9262 end\
9263\
9264 for _,v in pairs(self.values) do\
9265 if v.index == t.slotIndex then\
9266 v.selected = true\
9267 end\
9268 if v.key then\
9269 v.key = itemDB:getName(v.key)\
9270 end\
9271 end\
9272 end\
9273 self:adjustWidth()\
9274 self:update()\
9275 UI.ScrollingGrid.draw(self)\
9276 end,\
9277 eventHandler = function(self, event)\
9278 if event.type == 'grid_select' then\
9279 local fn = string.format('turtle.select(%d)', event.selected.index)\
9280 page:runFunction(fn)\
9281 else\
9282 return UI.ScrollingGrid.eventHandler(self, event)\
9283 end\
9284 return true\
9285 end,\
9286 },\
9287 },\
9288 UI.Tab {\
9289 title = 'Action',\
9290 backgroundColor = 'primary',\
9291 moveUp = UI.Button {\
9292 x = 5, y = 2,\
9293 text = 'up',\
9294 fn = 'turtle.up',\
9295 },\
9296 moveDown = UI.Button {\
9297 x = 5, y = 4,\
9298 text = 'dn',\
9299 fn = 'turtle.down',\
9300 },\
9301 moveForward = UI.Button {\
9302 x = 9, y = 3,\
9303 text = 'f',\
9304 fn = 'turtle.forward',\
9305 },\
9306 moveBack = UI.Button {\
9307 x = 2, y = 3,\
9308 text = 'b',\
9309 fn = 'turtle.back',\
9310 },\
9311 turnLeft = UI.Button {\
9312 x = 2, y = 6,\
9313 text = 'lt',\
9314 fn = 'turtle.turnLeft',\
9315 },\
9316 turnRight = UI.Button {\
9317 x = 8, y = 6,\
9318 text = 'rt',\
9319 fn = 'turtle.turnRight',\
9320 },\
9321 info = UI.TextArea {\
9322 x = 15, y = 1,\
9323 inactive = true,\
9324 },\
9325 showBlocks = function(self)\
9326 local script = [[\
9327 local function inspect(direction)\
9328 local s,b = turtle['inspect' .. (direction or '')]()\
9329 if not s then\
9330 return 'minecraft:air:0'\
9331 end\
9332 return string.format('%s:%d', b.name, b.metadata)\
9333 end\
9334\
9335 local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')\
9336 return string.format('%s\\n%s\\n%s', bu, bf, bd)\
9337 ]]\
9338\
9339 local s, m = page:runFunction(script, true)\
9340 self.info:setText(s or m)\
9341 end,\
9342 eventHandler = function(self, event)\
9343 if event.type == 'button_press' then\
9344 if event.button.fn then\
9345 page:runFunction(event.button.fn, event.button.nowrap)\
9346 self:showBlocks()\
9347 end\
9348 return true\
9349 end\
9350 return UI.Tab.eventHandler(self, event)\
9351 end,\
9352 },\
9353 enable = function(self)\
9354 if config.tab then\
9355 self:selectTab(Util.find(self, 'title', config.tab))\
9356 end\
9357 UI.Tabs.enable(self)\
9358 end\
9359 },\
9360 statusBar = UI.StatusBar {\
9361 values = { },\
9362 columns = {\
9363 { key = 'status' },\
9364 { key = 'distance', width = 6 },\
9365 { key = 'fuel', width = 6 },\
9366 },\
9367 draw = function(self)\
9368 local t = turtle\
9369 if t then\
9370 self.values.status = t.status\
9371 self.values.distance = t.distance and Util.round(t.distance, 2)\
9372 self.values.fuel = Util.toBytes(t.fuel)\
9373 end\
9374 UI.StatusBar.draw(self)\
9375 end,\
9376 },\
9377 notification = UI.Notification(),\
9378 accelerators = {\
9379 [ 'control-q' ] = 'quit',\
9380 },\
9381}\
9382\
9383function page:runFunction(script, nowrap)\
9384 for _ = 1, 2 do\
9385 if not socket then\
9386 socket = Socket.connect(turtle.id, 161)\
9387 end\
9388\
9389 if socket then\
9390 if not nowrap then\
9391 script = 'turtle.run(' .. script .. ')'\
9392 end\
9393 if socket:write({ type = 'scriptEx', args = script }) then\
9394 local t = socket:read(3)\
9395 if t then\
9396 return table.unpack(t)\
9397 end\
9398 return false, 'Socket timeout'\
9399 end\
9400 end\
9401 socket = nil\
9402 end\
9403 self.notification:error('Unable to connect')\
9404end\
9405\
9406function page:runScript(scriptName)\
9407 if turtle then\
9408 self.notification:info('Connecting')\
9409 self:sync()\
9410\
9411 local script = Util.readFile(fs.combine(SCRIPTS_PATH, scriptName))\
9412 if not script then\
9413 print('Unable to read script file')\
9414 end\
9415\
9416 local function processVariables()\
9417 local variables = {\
9418 COMPUTER_ID = os.getComputerID,\
9419 GPS = function()\
9420 local pt = require('opus.gps').getPoint()\
9421 if not pt then\
9422 error('Unable to determine location')\
9423 end\
9424 return _G.textutils.serialize(pt)\
9425 end,\
9426 }\
9427 for k,v in pairs(variables) do\
9428 local token = string.format('{%s}', k)\
9429 if script:find(token, 1, true) then\
9430 local s, m = pcall(v)\
9431 if not s then\
9432 self.notification:error(m)\
9433 return\
9434 end\
9435 script = script:gsub(token, m)\
9436 end\
9437 end\
9438 return true\
9439 end\
9440\
9441 if processVariables(script) then\
9442 local socket = Socket.connect(turtle.id, 161)\
9443 if not socket then\
9444 self.notification:error('Unable to connect')\
9445 return\
9446 end\
9447 socket:write({ type = 'script', args = script })\
9448 socket:close()\
9449\
9450 self.notification:success('Sent')\
9451 end\
9452 end\
9453end\
9454\
9455function page:eventHandler(event)\
9456 if event.type == 'quit' then\
9457 UI:quit()\
9458\
9459 elseif event.type == 'tab_select' then\
9460 config.tab = event.button.text\
9461 Config.update('Turtles', config)\
9462\
9463 else\
9464 return UI.Page.eventHandler(self, event)\
9465 end\
9466 return true\
9467end\
9468\
9469if not Util.getOptions(options, { ... }, true) then\
9470 return\
9471end\
9472\
9473if options.turtle.value >= 0 then\
9474 for _ = 1, 10 do\
9475 turtle = _G.network[options.turtle.value]\
9476 if turtle then\
9477 break\
9478 end\
9479 os.sleep(1)\
9480 end\
9481end\
9482\
9483Event.onInterval(1, function()\
9484 if turtle then\
9485 --local t = _G.network[turtle.id]\
9486 --turtle = t\
9487 page:draw()\
9488 page:sync()\
9489 end\
9490end)\
9491\
9492UI:setPage(page)\
9493UI:start()",
9494 [ "core/apis/swarm.lua" ] = "local class = require('opus.class')\
9495local Event = require('opus.event')\
9496local Map = require('opus.map')\
9497local Proxy = require('core.proxy')\
9498\
9499local Swarm = class()\
9500function Swarm:init(args)\
9501 self.pool = { }\
9502 Map.merge(self, args)\
9503end\
9504\
9505function Swarm:add(id, args)\
9506 local member = Map.shallowCopy(args or { })\
9507 member.id = id\
9508 self.pool[id] = member\
9509end\
9510\
9511function Swarm:remove(id, s, m)\
9512 local member = self.pool[id]\
9513 if member then\
9514 self.pool[id] = nil\
9515 self:onRemove(member, s, m)\
9516 if member.socket then\
9517 member.socket:close()\
9518 member.socket = nil\
9519 end\
9520 if member.handler then\
9521 member.handler:terminate()\
9522 member.handler = nil\
9523 end\
9524 end\
9525end\
9526\
9527function Swarm:run(fn)\
9528 for id, member in pairs(self.pool) do\
9529 if not member.socket then\
9530 member.handler = Event.addRoutine(function()\
9531 local s, m = pcall(function()\
9532 member.turtle, member.socket = Proxy.create(id, 'turtle')\
9533\
9534 fn(member)\
9535 end)\
9536 self:remove(id, s, m)\
9537 end)\
9538 end\
9539 end\
9540end\
9541\
9542function Swarm:stop()\
9543 for _, member in pairs(self.pool) do\
9544 if member.socket then\
9545 member.socket:close()\
9546 member.socket = nil\
9547 end\
9548 end\
9549end\
9550\
9551-- Override\
9552function Swarm:onRemove(member, success, msg)\
9553 print('removed from pool: ' .. member.id)\
9554 if not success then\
9555 _G.printError(msg)\
9556 end\
9557end\
9558\
9559return Swarm",
9560 [ "common/etc/scripts/moveTo.lua" ] = "local turtle = _G.turtle\
9561\
9562turtle.run(function()\
9563 local GPS = require('opus.gps')\
9564\
9565 if not turtle.enableGPS() then\
9566 error('turtle: No GPS found')\
9567 end\
9568\
9569 local pt = {GPS}\
9570\
9571 if not turtle.pathfind(pt) then\
9572 error('Unable to go to location')\
9573 end\
9574end)",
9575 [ "monitor/mirror.lua" ] = "local Terminal = require('opus.terminal')\
9576local Util = require('opus.util')\
9577\
9578local device = _G.device\
9579local os = _G.os\
9580local parallel = _G.parallel\
9581local shell = _ENV.shell\
9582local term = _G.term\
9583\
9584-- Example usage: mirror -r -e \"vnc 1\"\
9585\
9586local options = {\
9587 scale = { arg = 's', type = 'flag', value = false,\
9588 desc = 'Set monitor to .5 text scaling' },\
9589 resize = { arg = 'r', type = 'flag', value = false,\
9590 desc = 'Resize terminal to monitor size' },\
9591 execute = { arg = 'e', type = 'string',\
9592 desc = 'Execute a program' },\
9593 monitor = { arg = 'm', type = 'string', value = 'monitor',\
9594 desc = 'Name of monitor' },\
9595 help = { arg = 'h', type = 'flag', value = false,\
9596 desc = 'Displays the options' },\
9597}\
9598\
9599local args = { ... }\
9600if not Util.getOptions(options, args) then\
9601 return\
9602end\
9603\
9604local mon\
9605for k,v in pairs(device) do\
9606 if k == options.monitor.value or v.side == options.monitor.value then\
9607 mon = v\
9608 break\
9609 end\
9610end\
9611\
9612if not mon then\
9613 error('mirror: Invalid device')\
9614end\
9615\
9616mon.clear()\
9617\
9618mon.setTextScale(options.scale.value and .5 or 1)\
9619mon.setCursorPos(1, 1)\
9620\
9621local oterm = term.current()\
9622\
9623if options.resize.value then\
9624 oterm.reposition(1, 1, mon.getSize())\
9625end\
9626\
9627local mirror = Terminal.mirror(term.current(), mon)\
9628\
9629term.redirect(mirror)\
9630\
9631if options.execute.value then\
9632 parallel.waitForAny(\
9633 function()\
9634 shell.run(options.execute.value)\
9635 end,\
9636\
9637 function()\
9638 while true do\
9639 local event, side, x, y = os.pullEventRaw('monitor_touch')\
9640\
9641 if event == 'monitor_touch' and side == mon.side then\
9642 os.queueEvent('mouse_click', 1, x, y + 1)\
9643 os.queueEvent('mouse_up', 1, x, y + 1)\
9644 end\
9645 end\
9646 end\
9647 )\
9648\
9649 term.redirect(oterm)\
9650\
9651 mon.setCursorBlink(false)\
9652end",
9653 [ "common/etc/sounds.txt" ] = "block.anvil.break\
9654block.anvil.destroy\
9655block.anvil.fall\
9656block.anvil.hit\
9657block.anvil.land\
9658block.anvil.place\
9659block.anvil.step\
9660block.anvil.use\
9661block.boat.place\
9662entity.boat.paddle_land\
9663entity.boat.paddle_water\
9664block.brewing_stand.brew\
9665block.chest.close\
9666block.chest.locked\
9667block.chest.open\
9668block.cloth.break\
9669block.cloth.fall\
9670block.cloth.hit\
9671block.cloth.place\
9672block.cloth.step\
9673block.comparator.click\
9674block.dispenser.dispense\
9675block.dispenser.fail\
9676block.dispenser.launch\
9677block.enchantment_table.use\
9678block.end_gateway.spawn\
9679block.end_portal.spawn\
9680block.end_portal_frame.fill\
9681block.enderchest.close\
9682block.enderchest.open\
9683block.fence_gate.close\
9684block.fence_gate.open\
9685block.fire.ambient\
9686block.fire.extinguish\
9687block.furnace.fire_crackle\
9688block.glass.break\
9689block.glass.fall\
9690block.glass.hit\
9691block.glass.place\
9692block.glass.step\
9693block.grass.break\
9694block.grass.fall\
9695block.grass.hit\
9696block.grass.place\
9697block.grass.step\
9698block.gravel.break\
9699block.gravel.fall\
9700block.gravel.hit\
9701block.gravel.place\
9702block.gravel.step\
9703block.iron_door.close\
9704block.iron_door.open\
9705block.ladder.break\
9706block.ladder.fall\
9707block.ladder.hit\
9708block.ladder.place\
9709block.ladder.step\
9710block.lava.ambient\
9711block.lava.extinguish\
9712block.lava.pop\
9713block.lever.click\
9714block.metal.break\
9715block.metal.fall\
9716block.metal.hit\
9717block.metal.place\
9718block.metal.step\
9719block.metal_pressureplate.click_off\
9720block.metal_pressureplate.click_on\
9721block.note.basedrum\
9722block.note.bass\
9723block.note.bell\
9724block.note.chime\
9725block.note.flute\
9726block.note.guitar\
9727block.note.harp\
9728block.note.hat\
9729block.note.pling\
9730block.note.snare\
9731block.note.xylophone\
9732block.piston.contract\
9733block.piston.extend\
9734block.portal.ambient\
9735block.portal.travel\
9736block.portal.trigger\
9737block.redstone_torch.burnout\
9738block.sand.break\
9739block.sand.fall\
9740block.sand.hit\
9741block.sand.place\
9742block.sand.step\
9743block.shulker_box.close\
9744block.shulker_box.open\
9745block.slime.break\
9746block.slime.fall\
9747block.slime.hit\
9748block.slime.place\
9749block.slime.step\
9750block.snow.break\
9751block.snow.fall\
9752block.snow.hit\
9753block.snow.place\
9754block.snow.step\
9755block.stone.break\
9756block.stone.fall\
9757block.stone.hit\
9758block.stone.place\
9759block.stone.step\
9760block.stone_button.click_off\
9761block.stone_button.click_on\
9762block.stone_pressureplate.click_off\
9763block.stone_pressureplate.click_on\
9764block.wooden_trapdoor.close\
9765block.wooden_trapdoor.open\
9766block.iron_trapdoor.close\
9767block.iron_trapdoor.open\
9768block.tripwire.attach\
9769block.tripwire.click_off\
9770block.tripwire.click_on\
9771block.tripwire.detach\
9772block.water.ambient\
9773block.waterlily.place\
9774block.wood.break\
9775block.wood.fall\
9776block.wood.hit\
9777block.wood.place\
9778block.wood.step\
9779block.wood_button.click_off\
9780block.wood_button.click_on\
9781block.wood_pressureplate.click_off\
9782block.wood_pressureplate.click_on\
9783block.wooden_door.close\
9784block.wooden_door.open\
9785entity.arrow.hit\
9786entity.arrow.shoot\
9787entity.arrow.successful_hit\
9788entity.bat.ambient\
9789entity.armorstand.break\
9790entity.armorstand.fall\
9791entity.armorstand.hit\
9792entity.armorstand.place\
9793entity.bat.death\
9794entity.bat.hurt\
9795entity.bat.takeoff\
9796entity.blaze.ambient\
9797entity.blaze.burn\
9798entity.blaze.death\
9799entity.blaze.hurt\
9800entity.blaze.shoot\
9801entity.bobber.splash\
9802entity.bobber.throw\
9803entity.bobber.retrieve\
9804entity.cat.ambient\
9805entity.cat.death\
9806entity.cat.hiss\
9807entity.cat.hurt\
9808entity.cat.purr\
9809entity.cat.purreow\
9810entity.chicken.ambient\
9811entity.chicken.death\
9812entity.chicken.egg\
9813entity.chicken.hurt\
9814entity.chicken.step\
9815entity.cow.ambient\
9816entity.cow.death\
9817entity.cow.hurt\
9818entity.cow.milk\
9819entity.cow.step\
9820entity.creeper.death\
9821entity.creeper.hurt\
9822entity.creeper.primed\
9823entity.donkey.ambient\
9824entity.donkey.angry\
9825entity.donkey.chest\
9826entity.donkey.death\
9827entity.donkey.hurt\
9828entity.egg.throw\
9829entity.elder_guardian.ambient\
9830entity.elder_guardian.ambient_land\
9831entity.elder_guardian.curse\
9832entity.elder_guardian.death\
9833entity.elder_guardian.death_land\
9834entity.elder_guardian.hurt\
9835entity.elder_guardian.hurt_land\
9836entity.enderdragon.ambient\
9837entity.enderdragon.death\
9838entity.enderdragon.flap\
9839entity.enderdragon.growl\
9840entity.enderdragon.hurt\
9841entity.enderdragon.shoot\
9842entity.enderdragon_fireball.explode\
9843entity.endereye.launch\
9844entity.endereye.death\
9845entity.endermen.death\
9846entity.endermen.hurt\
9847entity.endermen.scream\
9848entity.endermen.stare\
9849entity.endermen.teleport\
9850entity.endermite.ambient\
9851entity.endermite.death\
9852entity.endermite.hurt\
9853entity.endermite.step\
9854entity.enderpearl.throw\
9855entity.evokation_illager.ambient\
9856entity.evokation_illager.hurt\
9857entity.evokation_illager.death\
9858entity.evokation_illager.cast_spell\
9859entity.evokation_illager.prepare_attack\
9860entity.evokation_illager.prepare_summon\
9861entity.evokation_illager.cast_spell\
9862entity.evokation_fangs.attack\
9863entity.experience_bottle.throw\
9864entity.experience_orb.pickup\
9865entity.firework.blast\
9866entity.firework.blast_far\
9867entity.firework.large_blast\
9868entity.firework.large_blast_far\
9869entity.firework.launch\
9870entity.firework.shoot\
9871entity.firework.twinkle\
9872entity.firework.twinkle_far\
9873entity.generic.big_fall\
9874entity.generic.burn\
9875entity.generic.death\
9876entity.generic.drink\
9877entity.generic.eat\
9878entity.generic.explode\
9879entity.generic.extinguish_fire\
9880entity.generic.hurt\
9881entity.generic.small_fall\
9882entity.generic.splash\
9883entity.generic.swim\
9884entity.ghast.ambient\
9885entity.ghast.death\
9886entity.ghast.hurt\
9887entity.ghast.shoot\
9888entity.ghast.warn\
9889entity.guardian.ambient\
9890entity.guardian.ambient_land\
9891entity.guardian.attack\
9892entity.guardian.death\
9893entity.guardian.death_land\
9894entity.guardian.flop\
9895entity.guardian.hurt\
9896entity.guardian.hurt_land\
9897entity.horse.ambient\
9898entity.horse.angry\
9899entity.horse.armor\
9900entity.horse.breathe\
9901entity.horse.death\
9902entity.horse.eat\
9903entity.horse.gallop\
9904entity.horse.hurt\
9905entity.horse.jump\
9906entity.horse.land\
9907entity.horse.saddle\
9908entity.horse.step\
9909entity.horse.step_wood\
9910entity.hostile.big_fall\
9911entity.hostile.death\
9912entity.hostile.hurt\
9913entity.hostile.hurt\
9914entity.hostile.splash\
9915entity.hostile.swim\
9916entity.husk.ambient\
9917entity.husk.death\
9918entity.husk.hurt\
9919entity.husk.step\
9920entity.illusion_illager.ambient\
9921entity.illusion_illager.cast_spell\
9922entity.illusion_illager.death\
9923entity.illusion_illager.hurt\
9924entity.illusion_illager.mirror_moveentity.illusion_illager.prepare_blindness\
9925entity.illusion_illager.prepare_mirror\
9926entity.irongolem.attack\
9927entity.irongolem.death\
9928entity.irongolem.hurt\
9929entity.irongolem.step\
9930entity.item.break\
9931entity.item.pickup\
9932entity.itemframe.add_item\
9933entity.itemframe.break\
9934entity.itemframe.place\
9935entity.itemframe.remove_item\
9936entity.itemframe.rotate_item\
9937entity.llama.ambient\
9938entity.llama.angry\
9939entity.llama.death\
9940entity.llama.eat\
9941entity.llama.hurt\
9942entity.llama.spit\
9943entity.llama.step\
9944entity.llama.swag\
9945entity.leashknot.break\
9946entity.leashknot.place\
9947entity.lightning.impact\
9948entity.lightning.thunder\
9949entity.lingeringpotion.throw\
9950entity.magmacube.death\
9951entity.magmacube.hurt\
9952entity.magmacube.jump\
9953entity.magmacube.squish\
9954entity.minecart.inside\
9955entity.minecart.riding\
9956entity.mooshroom.shear\
9957entity.mule.ambient\
9958entity.mule.death\
9959entity.mule.hurt\
9960entity.painting.break\
9961entity.painting.place\
9962entity.parrot.ambient\
9963entity.parrot.death\
9964entity.parrot.eat\
9965entity.parrot.fly\
9966entity.parrot.hurt\
9967entity.parrot.step\
9968entity.pig.ambient\
9969entity.pig.death\
9970entity.pig.hurt\
9971entity.pig.saddle\
9972entity.pig.step\
9973entity.player.attack.crit\
9974entity.player.attack.knockback\
9975entity.player.attack.nodamage\
9976entity.player.attack.strong\
9977entity.player.attack.sweep\
9978entity.player.attack.weak\
9979entity.player.big_fall\
9980entity.player.burp\
9981entity.player.death\
9982entity.player.hurt\
9983entity.player.levelup\
9984entity.player.small_fall\
9985entity.player.splash\
9986entity.player.swim\
9987entity.polar_bear.ambient\
9988entity.polar_bear.baby_ambient\
9989entity.polar_bear.death\
9990entity.polar_bear.hurt\
9991entity.polar_bear.step\
9992entity.polar_bear.warning\
9993entity.rabbit.attack\
9994entity.rabbit.ambient\
9995entity.rabbit.death\
9996entity.rabbit.hurt\
9997entity.rabbit.jump\
9998entity.sheep.death\
9999entity.sheep.hurt\
10000entity.sheep.shear\
10001entity.sheep.step\
10002entity.shield.break\
10003entity.shield.block\
10004entity.shulker.ambient\
10005entity.shulker_bullet.hit\
10006entity.shulker_bullet.hurt\
10007entity.shulker.death\
10008entity.shulker.close\
10009entity.shulker.hit\
10010entity.shulker.hurt\
10011entity.shulker.hurt_closed\
10012entity.shulker.shoot\
10013entity.shulker.teleport\
10014entity.silverfish.ambient\
10015entity.silverfish.death\
10016entity.silverfish.hurt\
10017entity.silverfish.step\
10018entity.skeleton.ambient\
10019entity.skeleton.death\
10020entity.skeleton.hurt\
10021entity.skeleton.shoot\
10022entity.skeleton.step\
10023entity.skeleton_horse.ambient\
10024entity.skeleton_horse.death\
10025entity.skeleton_horse.hurt\
10026entity.slime.attack\
10027entity.slime.death\
10028entity.slime.hurt\
10029entity.slime.jump\
10030entity.slime.squish\
10031entity.small_magmacube.death\
10032entity.small_magmacube.hurt\
10033entity.small_magmacube.squish\
10034entity.small_slime.death\
10035entity.small_slime.hurt\
10036entity.small_slime.squish\
10037entity.snowball.throw\
10038entity.snowman.ambient\
10039entity.snowman.death\
10040entity.snowman.hurt\
10041entity.snowman.shoot\
10042entity.spider.ambient\
10043entity.spider.death\
10044entity.spider.hurt\
10045entity.spider.step\
10046entity.splash_potion.break\
10047entity.splash_potion.throw\
10048entity.squid.ambient\
10049entity.squid.death\
10050entity.squid.hurt\
10051entity.stray.ambient\
10052entity.stray.death\
10053entity.stray.hurt\
10054entity.stray.step\
10055entity.tnt.primed\
10056entity.vex.ambient\
10057entity.vex.charge\
10058entity.vex.hurt\
10059entity.vex.death\
10060entity.vindication_illager.ambient\
10061entity.vindication_illager.hurt\
10062entity.vindication_illager.death\
10063entity.villager.ambient\
10064entity.villager.death\
10065entity.villager.hurt\
10066entity.villager.no\
10067entity.villager.trading\
10068entity.villager.yes\
10069entity.witch.ambient\
10070entity.witch.death\
10071entity.witch.drink\
10072entity.witch.hurt\
10073entity.witch.throw\
10074entity.wither.ambient\
10075entity.wither.break_block\
10076entity.wither.death\
10077entity.wither.hurt\
10078entity.wither.shoot\
10079entity.wither.spawn\
10080entity.wither_skeleton.ambient\
10081entity.wither_skeleton.death\
10082entity.wither_skeleton.hurt\
10083entity.wither_skeleton.step\
10084entity.wolf.ambient\
10085entity.wolf.death\
10086entity.wolf.growl\
10087entity.wolf.hurt\
10088entity.wolf.pant\
10089entity.wolf.shake\
10090entity.wolf.step\
10091entity.wolf.whine\
10092entity.zombie.ambient\
10093entity.zombie.attack_door_wood\
10094entity.zombie.attack_iron_door\
10095entity.zombie.break_door_wood\
10096entity.zombie.cure\
10097entity.zombie.death\
10098entity.zombie.hurt\
10099entity.zombie.step\
10100entity.zombie_horse.ambient\
10101entity.zombie_horse.death\
10102entity.zombie_horse.hurt\
10103entity.zombie.infect\
10104entity.zombie_pig.ambient\
10105entity.zombie_pig.angry\
10106entity.zombie_pig.death\
10107entity.zombie_pig.hurt\
10108enchant.thorns.hit\
10109music.creative\
10110music.credits\
10111music.dragon\
10112music.end\
10113music.game\
10114music.menu\
10115music.nether\
10116record.11\
10117record.13\
10118record.blocks\
10119record.cat\
10120record.chirp\
10121record.far\
10122record.mall\
10123record.mellohi\
10124record.stal\
10125record.strad\
10126record.wait\
10127record.ward\
10128item.armor.equip_chain\
10129item.armor.equip_diamond\
10130item.armor.equip_generic\
10131item.armor.equip_gold\
10132item.armor.equip_iron\
10133item.armor.equip_leather\
10134item.bottle.fill\
10135item.bottle.fill_dragonbreath\
10136item.bucket.empty\
10137item.bucket.empty_lava\
10138item.bucket.fill\
10139item.bucket.fill_lava\
10140item.chorus_fruit.teleport\
10141item.elytra.flying\
10142item.firecharge.use\
10143item.flintandsteel.use\
10144item.hoe.till\
10145item.shovel.flatten\
10146item.totem.use\
10147weather.rain\
10148weather.rain.above\
10149ambient.cave\
10150ui.button.click",
10151 [ "common/edit.lua" ] = "local Array = require('opus.array')\
10152local Config = require('opus.config')\
10153local fuzzy = require('opus.fuzzy')\
10154local UI = require('opus.ui')\
10155local Util = require('opus.util')\
10156\
10157local device = _G.device\
10158local fs = _G.fs\
10159local keys = _G.keys\
10160local multishell = _ENV.multishell\
10161local os = _G.os\
10162local shell = _ENV.shell\
10163local term = _G.term.current()\
10164local textutils = _G.textutils\
10165\
10166local _format = string.format\
10167local _rep = string.rep\
10168local _sub = string.sub\
10169local _concat = table.concat\
10170local _insert = table.insert\
10171local _remove = table.remove\
10172local _unpack = table.unpack\
10173\
10174local config = Config.load('editor')\
10175\
10176local x, y = 1, 1\
10177local w, h = term.getSize()\
10178local scrollX = 0\
10179local scrollY = 0\
10180local lastPos = { x = 1, y = 1 }\
10181local tLines = { }\
10182local fileInfo\
10183local actions\
10184local lastSave\
10185local dirty = { y = 1, ey = h }\
10186local mark = { }\
10187local searchPattern\
10188local undo = { chain = { }, redo = { } }\
10189\
10190h = h - 1\
10191\
10192local bgColor = 'gray'\
10193local color = {\
10194 text = '0',\
10195 keyword = '2',\
10196 comment = 'd',\
10197 string = '1',\
10198 mark = '8',\
10199 bg = '7',\
10200}\
10201\
10202if not term.isColor() then\
10203 bgColor = 'black'\
10204 color = {\
10205 text = '0',\
10206 keyword = '8',\
10207 comment = '8',\
10208 string = '8',\
10209 mark = '7',\
10210 bg = 'f',\
10211 }\
10212end\
10213\
10214local keyMapping = {\
10215 -- movement\
10216 up = 'up',\
10217 down = 'down',\
10218 left = 'left',\
10219 right = 'right',\
10220 pageUp = 'page_up',\
10221 [ 'control-b' ] = 'page_up',\
10222 pageDown = 'page_down',\
10223 home = 'home',\
10224 [ 'end' ] = 'toend',\
10225 [ 'control-home' ] = 'top',\
10226 [ 'control-end' ] = 'bottom',\
10227 [ 'control-right' ] = 'word',\
10228 [ 'control-left' ] = 'backword',\
10229 [ 'scroll_up' ] = 'scroll_up',\
10230 [ 'control-up' ] = 'scroll_up',\
10231 [ 'scroll_down' ] = 'scroll_down',\
10232 [ 'control-down' ] = 'scroll_down',\
10233 [ 'mouse_click' ] = 'go_to',\
10234 [ 'control-g' ] = 'goto_line',\
10235\
10236 -- marking\
10237 [ 'shift-up' ] = 'mark_up',\
10238 [ 'shift-down' ] = 'mark_down',\
10239 [ 'shift-left' ] = 'mark_left',\
10240 [ 'shift-right' ] = 'mark_right',\
10241 [ 'mouse_drag' ] = 'mark_to',\
10242 [ 'shift-mouse_click' ] = 'mark_to',\
10243 [ 'control-a' ] = 'mark_all',\
10244 [ 'control-shift-right' ] = 'mark_word',\
10245 [ 'control-shift-left' ] = 'mark_backword',\
10246 [ 'shift-end' ] = 'mark_end',\
10247 [ 'shift-home' ] = 'mark_home',\
10248 [ 'mouse_down' ] = 'mark_anchor',\
10249 [ 'mouse_doubleclick' ] = 'mark_current_word',\
10250 [ 'mouse_tripleclick' ] = 'mark_line',\
10251\
10252 -- editing\
10253 delete = 'delete',\
10254 backspace = 'backspace',\
10255 enter = 'enter',\
10256 char = 'char',\
10257 paste = 'paste',\
10258 tab = 'tab',\
10259 [ 'control-z' ] = 'undo',\
10260 [ 'control-Z' ] = 'redo',\
10261 [ 'control-space' ] = 'autocomplete',\
10262 [ 'control-shift-space' ] = 'peripheral',\
10263\
10264 -- copy/paste\
10265 [ 'control-x' ] = 'cut',\
10266 [ 'control-c' ] = 'copy',\
10267 [ 'control-y' ] = 'paste_internal',\
10268\
10269 -- file\
10270 [ 'control-s' ] = 'save',\
10271 [ 'control-S' ] = 'save_as',\
10272 [ 'control-q' ] = 'exit',\
10273 [ 'control-enter' ] = 'run',\
10274 [ 'control-p' ] = 'quick_open',\
10275\
10276 -- search\
10277 [ 'control-f' ] = 'find_prompt',\
10278 [ 'control-slash' ] = 'find_prompt',\
10279 [ 'control-n' ] = 'find_next',\
10280\
10281 -- misc\
10282 [ 'control-i' ] = 'status',\
10283 [ 'control-r' ] = 'refresh',\
10284}\
10285\
10286local page = UI.Page {\
10287 menuBar = UI.MenuBar {\
10288 transitionHint = 'slideLeft',\
10289 buttons = {\
10290 { text = 'File', dropdown = {\
10291 { text = 'New ', event = 'menu_action', action = 'file_new' },\
10292 { text = 'Open... ', event = 'menu_action', action = 'file_open' },\
10293 { text = 'Quick Open... ^p', event = 'menu_action', action = 'quick_open' },\
10294 { text = 'Recent... ', event = 'menu_action', action = 'recent' },\
10295 { spacer = true },\
10296 { text = 'Save ^s', event = 'menu_action', action = 'save' },\
10297 { text = 'Save As... ^S', event = 'menu_action', action = 'save_as' },\
10298 { spacer = true },\
10299 { text = 'Quit ^q', event = 'menu_action', action = 'exit' },\
10300 } },\
10301 { text = 'Edit', dropdown = {\
10302 { text = 'Cut ^x', event = 'menu_action', action = 'cut' },\
10303 { text = 'Copy ^c', event = 'menu_action', action = 'copy' },\
10304 { text = 'Paste ^y,^V', event = 'menu_action', action = 'paste_internal' },\
10305 { spacer = true },\
10306 { text = 'Find... ^f', event = 'menu_action', action = 'find_prompt' },\
10307 { text = 'Find Next ^n', event = 'menu_action', action = 'find_next' },\
10308 { spacer = true },\
10309 { text = 'Go to line... ^g', event = 'menu_action', action = 'goto_line' },\
10310 { text = 'Mark all ^a', event = 'menu_action', action = 'mark_all' },\
10311 } },\
10312 { text = 'Code', dropdown = {\
10313 { text = 'Complete ^space', event = 'menu_action', action = 'autocomplete' },\
10314 { text = 'Run ^enter', event = 'menu_action', action = 'run' },\
10315 { spacer = true },\
10316 { text = 'Peripheral ^SPACE', event = 'menu_action', action = 'peripheral' },\
10317 } },\
10318 },\
10319 status = UI.Text {\
10320 textColor = 'gray',\
10321 x = -9, width = 9,\
10322 align = 'right',\
10323 },\
10324 },\
10325 gotoLine = UI.MiniSlideOut {\
10326 x = -15, y = -2,\
10327 label = 'Line',\
10328 lineNo = UI.TextEntry {\
10329 x = 7, width = 7,\
10330 limit = 5,\
10331 transform = 'number',\
10332 accelerators = {\
10333 [ 'enter' ] = 'accept',\
10334 },\
10335 },\
10336 show = function(self)\
10337 self.lineNo:reset()\
10338 UI.MiniSlideOut.show(self)\
10339 end,\
10340 eventHandler = function(self, event)\
10341 if event.type == 'accept' then\
10342 if self.lineNo.value then\
10343 actions.process('go_to', 1, self.lineNo.value)\
10344 end\
10345 self:hide()\
10346 return true\
10347 end\
10348 return UI.MiniSlideOut.eventHandler(self, event)\
10349 end,\
10350 },\
10351 search = UI.MiniSlideOut {\
10352 x = '50%', y = -2,\
10353 label = 'Find',\
10354 search = UI.TextEntry {\
10355 x = 7, ex = -3,\
10356 accelerators = {\
10357 [ 'enter' ] = 'accept',\
10358 },\
10359 },\
10360 show = function(self)\
10361 self.search:markAll()\
10362 UI.MiniSlideOut.show(self)\
10363 end,\
10364 eventHandler = function(self, event)\
10365 if event.type == 'accept' then\
10366 local text = self.search.value\
10367 if text and #text > 0 then\
10368 searchPattern = text:lower()\
10369 if searchPattern then\
10370 actions.unmark()\
10371 actions.process('find', searchPattern, x)\
10372 end\
10373 end\
10374 self:hide()\
10375 return true\
10376 end\
10377 return UI.MiniSlideOut.eventHandler(self, event)\
10378 end,\
10379 },\
10380 save_as = UI.MiniSlideOut {\
10381 x = '30%', y = -2,\
10382 label = 'Save',\
10383 filename = UI.TextEntry {\
10384 x = 7, ex = -3,\
10385 accelerators = {\
10386 [ 'enter' ] = 'accept',\
10387 },\
10388 },\
10389 show = function(self)\
10390 self.filename:setValue(fileInfo.path)\
10391 self.filename:setPosition(#self.filename.value)\
10392 UI.MiniSlideOut.show(self)\
10393 end,\
10394 eventHandler = function(self, event)\
10395 if event.type == 'accept' then\
10396 local text = self.filename.value\
10397 if text and #text > 0 then\
10398 actions.save('/' .. text)\
10399 end\
10400 self:hide()\
10401 return true\
10402 end\
10403 return UI.MiniSlideOut.eventHandler(self, event)\
10404 end,\
10405 },\
10406 unsaved = UI.Question {\
10407 x = -25, y = -2,\
10408 label = 'Save',\
10409 cancel = UI.Button {\
10410 x = 16,\
10411 text = 'Cancel',\
10412 backgroundColor = 'primary',\
10413 event = 'question_cancel',\
10414 },\
10415 show = function(self, action)\
10416 self.action = action\
10417 UI.MiniSlideOut.show(self)\
10418 end,\
10419 eventHandler = function(self, event)\
10420 if event.type == 'question_yes' then\
10421 if actions.save() then\
10422 self:hide()\
10423 actions.process(self.action)\
10424 end\
10425 elseif event.type == 'question_no' then\
10426 actions.process(self.action, true)\
10427 self:hide()\
10428 elseif event.type == 'question_cancel' then\
10429 self:hide()\
10430 end\
10431 return UI.MiniSlideOut.eventHandler(self, event)\
10432 end,\
10433 },\
10434 file_open = UI.FileSelect {\
10435 modal = true,\
10436 enable = function() end,\
10437 show = function(self)\
10438 UI.FileSelect.enable(self, fs.getDir(fileInfo.path))\
10439 self:focusFirst()\
10440 self:draw()\
10441 self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })\
10442 end,\
10443 eventHandler = function(self, event)\
10444 if event.type == 'select_cancel' then\
10445 self:disable()\
10446 elseif event.type == 'select_file' then\
10447 self:disable()\
10448 actions.process('open', event.file)\
10449 end\
10450 return UI.FileSelect.eventHandler(self, event)\
10451 end,\
10452 },\
10453 recent = UI.SlideOut {\
10454 grid = UI.Grid {\
10455 x = 2, y = 2, ey = -4, ex = -2,\
10456 columns = {\
10457 { key = 'name', heading = 'Name' },\
10458 { key = 'dir', heading = 'Directory', textColor = 'lightGray' },\
10459 },\
10460 accelerators = {\
10461 backspace = 'slide_hide',\
10462 },\
10463 },\
10464 cancel = UI.Button {\
10465 x = -9, y = -2,\
10466 text = 'Cancel',\
10467 event = 'slide_hide',\
10468 },\
10469 show = function(self)\
10470 local t = { }\
10471 for _,v in pairs(config.recent or { }) do\
10472 _insert(t, { name = fs.getName(v), dir = fs.getDir(v), path = v })\
10473 end\
10474 self.grid:setValues(t)\
10475 self.grid:setIndex(1)\
10476 UI.SlideOut.show(self)\
10477 self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })\
10478 end,\
10479 eventHandler = function(self, event)\
10480 if event.type == 'grid_select' then\
10481 actions.process('open', event.selected.path)\
10482 self:hide()\
10483 return true\
10484 end\
10485 return UI.SlideOut.eventHandler(self, event)\
10486 end,\
10487 },\
10488 quick_open = UI.QuickSelect {\
10489 modal = true,\
10490 enable = function() end,\
10491 show = function(self)\
10492 UI.QuickSelect.enable(self)\
10493 self:focusFirst()\
10494 self:draw()\
10495 self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })\
10496 end,\
10497 eventHandler = function(self, event)\
10498 if event.type == 'select_cancel' then\
10499 self:disable()\
10500 elseif event.type == 'select_file' then\
10501 self:disable()\
10502 actions.process('open', event.file)\
10503 end\
10504 return UI.QuickSelect.eventHandler(self, event)\
10505 end,\
10506 },\
10507 completions = UI.SlideOut {\
10508 x = -12, y = 2,\
10509 transitionHint = 'slideLeft',\
10510 grid = UI.Grid {\
10511 x = 2, y = 2, ey = -2,\
10512 columns = {\
10513 { key = 'text', heading = 'Completion' },\
10514 },\
10515 accelerators = {\
10516 [ ' ' ] = 'down',\
10517 backspace = 'slide_hide',\
10518 },\
10519 },\
10520 cancel = UI.Button {\
10521 y = -1, x = -9,\
10522 text = 'Cancel',\
10523 backgroundColor = 'black',\
10524 backgroundFocusColor = 'black',\
10525 textColor = 'lightGray',\
10526 event = 'slide_hide',\
10527 },\
10528 show = function(self, values)\
10529 local m = Util.reduce(values, function(m, v)\
10530 return #v.text > m and #v.text or m\
10531 end, 12)\
10532 m = m + 3\
10533 m = m > self.parent.width and self.parent.width or m\
10534 self.ox = -m\
10535 self:resize()\
10536 self.grid:setValues(values)\
10537 self.grid:setIndex(1)\
10538 UI.SlideOut.show(self)\
10539 end,\
10540 eventHandler = function(self, event)\
10541 if event.type == 'grid_select' then\
10542 actions.process('insertText', x, y, event.selected.complete)\
10543 self:hide()\
10544 return true\
10545 end\
10546 return UI.SlideOut.eventHandler(self, event)\
10547 end,\
10548 },\
10549 peripheral = UI.SlideOut {\
10550 x = '20%', y = 2,\
10551 transitionHint = 'slideLeft',\
10552 grid1 = UI.Grid {\
10553 x = 2, y = 2, ey = 5,\
10554 sortColumn = 'name',\
10555 columns = {\
10556 { key = 'name', heading = 'Peripheral' },\
10557 },\
10558 accelerators = {\
10559 [ ' ' ] = 'down',\
10560 backspace = 'slide_hide',\
10561 grid_focus_row = 'select_peripheral',\
10562 grid_select = 'complete',\
10563 },\
10564 scan = function(self)\
10565 self.values = { }\
10566 for k, v in pairs(device) do\
10567 if type(v.side) == 'string' then\
10568 _insert(self.values, { name = k, complete = 'peripheral.wrap(\"' .. v.side .. '\")' })\
10569 end\
10570 end\
10571 end,\
10572 postInit = function(self)\
10573 self:scan()\
10574 end,\
10575 },\
10576 grid2 = UI.Grid {\
10577 x = 2, y = 6, ey = -2,\
10578 sortColumn = 'method',\
10579 columns = {\
10580 { key = 'method', heading = 'Method' },\
10581 },\
10582 accelerators = {\
10583 [ ' ' ] = 'down',\
10584 backspace = 'slide_hide',\
10585 grid_select = 'complete',\
10586 },\
10587 showMethods = function(self)\
10588 local dev = device[self.parent.grid1:getSelected().name]\
10589 local t = { }\
10590 if dev then\
10591 pcall(function()\
10592 local docs = dev.getDocs and dev.getDocs()\
10593 for k, v in pairs(dev) do\
10594 if type(v) == 'function' then\
10595 local m = docs and docs[k] and docs[k]:match('^function%((.+)%).+')\
10596 _insert(t, { method = k, complete = k .. '(' .. (m or '') .. ')' })\
10597 end\
10598 end\
10599 end)\
10600 end\
10601 self:setValues(t)\
10602 end,\
10603 enable = function(self)\
10604 self:showMethods()\
10605 UI.Grid.enable(self)\
10606 end,\
10607 },\
10608 cancel = UI.Button {\
10609 y = -1, x = -9,\
10610 text = 'Cancel',\
10611 backgroundColor = 'black',\
10612 backgroundFocusColor = 'black',\
10613 textColor = 'lightGray',\
10614 event = 'slide_hide',\
10615 },\
10616 eventHandler = function(self, event)\
10617 if event.type == 'complete' then\
10618 actions.process('insertText', x, y, event.selected.complete)\
10619 actions.process('left')\
10620 self:hide()\
10621 return true\
10622 elseif event.type == 'select_peripheral' then\
10623 self.grid2:showMethods()\
10624 self.grid2:setIndex(1)\
10625 self.grid2:update()\
10626 self.grid2:draw()\
10627 end\
10628 return UI.SlideOut.eventHandler(self, event)\
10629 end,\
10630 },\
10631 editor = UI.Window {\
10632 y = 2,\
10633 backgroundColor = bgColor,\
10634 transitionHint = 'slideRight',\
10635 cursorBlink = true,\
10636 focus = function(self)\
10637 if self.focused then\
10638 self:setCursorPos(x - scrollX, y - scrollY)\
10639 end\
10640 end,\
10641 resize = function(self)\
10642 UI.Window.resize(self)\
10643\
10644 w, h = self.width, self.height\
10645 actions.set_cursor(x, y)\
10646 actions.dirty_all()\
10647 actions.redraw()\
10648 end,\
10649 setCursorPos = function(self, cx, cy)\
10650 self.cursorBlink = cy >= 1 and cy <= self.height\
10651 UI.Window.setCursorPos(self, cx, cy)\
10652 end,\
10653 draw = function()\
10654 actions.redraw()\
10655 end,\
10656 eventHandler = function(_, event)\
10657 if event.ie then\
10658 local action, param, param2\
10659 local ie = event.ie\
10660\
10661 if ie.code == 'char' then\
10662 action = keyMapping.char\
10663 param = ie.ch\
10664\
10665 elseif ie.code == \"mouse_click\" or\
10666 ie.code == 'mouse_drag' or\
10667 ie.code == 'shift-mouse_click' or\
10668 ie.code == 'mouse_down' or\
10669 ie.code == 'mouse_doubleclick' then\
10670\
10671 action = keyMapping[ie.code]\
10672 param = ie.x + scrollX\
10673 param2 = ie.y + scrollY\
10674\
10675 elseif event.type == 'paste' then\
10676 action = keyMapping.paste\
10677 param = event.text\
10678\
10679 else\
10680 action = keyMapping[ie.code]\
10681 end\
10682\
10683 if action then\
10684 actions.process(action, param, param2)\
10685 return true\
10686 end\
10687 end\
10688 end,\
10689 },\
10690 notification = UI.Notification { },\
10691 enable = function(self)\
10692 UI.Page.enable(self)\
10693 self:setFocus(self.editor)\
10694 end,\
10695 checkFocus = function(self)\
10696 if not self.focused or not self.focused.enabled then\
10697 -- if no current focus, set it to the editor\
10698 self:setFocus(self.editor)\
10699 end\
10700 end,\
10701 eventHandler = function(self, event)\
10702 if event.type == 'menu_action' then\
10703 actions.process(event.element.action)\
10704 return true\
10705 end\
10706 return UI.Page.eventHandler(self, event)\
10707 end,\
10708}\
10709\
10710local function getFileInfo(path)\
10711 path = fs.combine('/', path)\
10712\
10713 local fi = {\
10714 path = path,\
10715 isNew = not fs.exists(path),\
10716 dirExists = fs.exists(fs.getDir(path)),\
10717 isReadOnly = fs.isReadOnly(path),\
10718 }\
10719\
10720 if path ~= config.filename then\
10721 config.filename = path\
10722 config.recent = config.recent or { }\
10723\
10724 Array.removeByValue(config.recent, path)\
10725 _insert(config.recent, 1, path)\
10726 while #config.recent > 10 do\
10727 _remove(config.recent)\
10728 end\
10729\
10730 Config.update('editor', config)\
10731 end\
10732\
10733 if multishell then\
10734 multishell.setTitle(multishell.getCurrent(), fs.getName(fi.path))\
10735 end\
10736\
10737 return fi\
10738end\
10739\
10740local keywords = Util.transpose {\
10741 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',\
10742 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while'\
10743}\
10744\
10745local function writeHighlighted(sLine, ny, dy)\
10746 local buffer = { fg = { }, text = { } }\
10747\
10748 local function tryWrite(line, regex, fgcolor)\
10749 local match = line:match(regex)\
10750 if match then\
10751 local fg = type(fgcolor) == \"string\" and fgcolor or fgcolor(match)\
10752 _insert(buffer.text, match)\
10753 _insert(buffer.fg, _rep(fg, #match))\
10754 return _sub(line, #match + 1)\
10755 end\
10756 return nil\
10757 end\
10758\
10759 while #sLine > 0 do\
10760 sLine =\
10761 -- tryWrite(sLine, \"^[%\\26]\", '7' ) or\
10762 tryWrite(sLine, \"^%-%-%[%[.-%]%]\", color.comment ) or\
10763 tryWrite(sLine, \"^%-%-.*\", color.comment ) or\
10764 tryWrite(sLine, \"^\\\".-[^\\\\]\\\"\", color.string ) or\
10765 tryWrite(sLine, \"^\\'.-[^\\\\]\\'\", color.string ) or\
10766 tryWrite(sLine, \"^%[%[.-%]%]\", color.string ) or\
10767 tryWrite(sLine, \"^[%w_]+\", function(match)\
10768 return keywords[match] and color.keyword or color.text\
10769 end) or\
10770 tryWrite(sLine, \"^[^%w_]\", color.text)\
10771 end\
10772\
10773 buffer.fg = _concat(buffer.fg) .. '7'\
10774 buffer.text = _concat(buffer.text) .. '\\183'\
10775\
10776 if mark.active and ny >= mark.y and ny <= mark.ey then\
10777 local sx = ny == mark.y and mark.x or 1\
10778 local ex = ny == mark.ey and mark.ex or #buffer.text\
10779 buffer.bg = _rep(color.bg, sx - 1) ..\
10780 _rep(color.mark, ex - sx) ..\
10781 _rep(color.bg, #buffer.text - ex + 1)\
10782 else\
10783 buffer.bg = _rep(color.bg, #buffer.text)\
10784 end\
10785\
10786 page.editor:blit(1 - scrollX, dy, buffer.text, buffer.bg, buffer.fg)\
10787end\
10788\
10789local function redraw()\
10790 if dirty.y > 0 then\
10791 for dy = 1, h do\
10792 local sLine = tLines[dy + scrollY]\
10793 if sLine and #sLine > 0 then\
10794 if dy + scrollY >= dirty.y and dy + scrollY <= dirty.ey then\
10795 page.editor:clearLine(dy)\
10796 writeHighlighted(sLine, dy + scrollY, dy)\
10797 end\
10798 else\
10799 page.editor:clearLine(dy)\
10800 end\
10801 end\
10802 end\
10803\
10804 local modifiedIndicator = undo.chain[#undo.chain] == lastSave and ' ' or '*'\
10805 page.menuBar.status.value = _format('%d:%d%s', y, x, modifiedIndicator)\
10806 page.menuBar.status:draw()\
10807\
10808 if page.editor.focused then\
10809 page.editor:setCursorPos(x - scrollX, y - scrollY)\
10810 end\
10811\
10812 dirty.y, dirty.ey = 0, 0\
10813end\
10814\
10815local function nextWord(line, cx)\
10816 local result = { line:find(\"(%w+)\", cx) }\
10817 if #result > 1 and result[2] > cx then\
10818 return result[2] + 1\
10819 elseif #result > 0 and result[1] == cx then\
10820 result = { line:find(\"(%w+)\", result[2] + 1) }\
10821 if #result > 0 then\
10822 return result[1]\
10823 end\
10824 end\
10825end\
10826\
10827actions = {\
10828 info = function(pattern, ...)\
10829 page.notification:info(_format(pattern, ...))\
10830 end,\
10831\
10832 error = function(pattern, ...)\
10833 page.notification:error(_format(pattern, ...))\
10834 end,\
10835\
10836 undo = function()\
10837 local last = _remove(undo.chain)\
10838 if last then\
10839 undo.active = true\
10840 _insert(undo.redo, { })\
10841 for i = #last, 1, -1 do\
10842 local u = last[i]\
10843 actions[u.action](_unpack(u.args))\
10844 end\
10845 undo.active = false\
10846 else\
10847 actions.info('already at oldest change')\
10848 end\
10849 end,\
10850\
10851 undo_add = function(entry)\
10852 if undo.active then\
10853 local last = undo.redo[#undo.redo]\
10854 _insert(last, entry)\
10855 else\
10856 if not undo.redo_active then\
10857 undo.redo = { }\
10858 end\
10859 local last = undo.chain[#undo.chain]\
10860 if last and undo.continue then\
10861 _insert(last, entry)\
10862 else\
10863 _insert(undo.chain, { entry })\
10864 end\
10865 end\
10866 end,\
10867\
10868 redo = function()\
10869 local last = _remove(undo.redo)\
10870 if last then\
10871 -- too many flags !\
10872 undo.redo_active = true\
10873 undo.continue = false\
10874 for i = #last, 1, -1 do\
10875 local u = last[i]\
10876 actions[u.action](_unpack(u.args))\
10877 undo.continue = true\
10878 end\
10879 undo.redo_active = false\
10880 else\
10881 actions.info('already at newest change')\
10882 end\
10883 end,\
10884\
10885 autocomplete = function()\
10886 local sLine = tLines[y]:sub(1, x - 1):match(\"[a-zA-Z0-9_%.]+$\")\
10887 local results = sLine and textutils.complete(sLine, _ENV) or { }\
10888\
10889 if #results == 0 then\
10890 actions.error('no completions available')\
10891\
10892 elseif #results == 1 then\
10893 actions.insertText(x, y, results[1])\
10894\
10895 elseif #results > 1 then\
10896 local prefix = sLine:match('^.+%.(.*)$') or sLine\
10897 for i = 1, #results do\
10898 results[i] = {\
10899 text = prefix .. results[i],\
10900 complete = results[i],\
10901 }\
10902 end\
10903 page.completions:show(results)\
10904 end\
10905 end,\
10906\
10907 peripheral = function()\
10908 page.peripheral:show()\
10909 end,\
10910\
10911 refresh = function()\
10912 actions.dirty_all()\
10913 mark.continue = mark.active\
10914 actions.info('refreshed')\
10915 end,\
10916\
10917 goto_line = function()\
10918 page.gotoLine:show()\
10919 end,\
10920\
10921 find = function(pattern, sx)\
10922 local nLines = #tLines\
10923 for i = 1, nLines + 1 do\
10924 local ny = y + i - 1\
10925 if ny > nLines then\
10926 ny = ny - nLines\
10927 end\
10928 local nx = tLines[ny]:lower():find(pattern, sx, true)\
10929 if nx then\
10930 if ny < y or ny == y and nx <= x then\
10931 actions.info('search hit BOTTOM, continuing at TOP')\
10932 end\
10933 actions.go_to(nx, ny)\
10934 actions.mark_to(nx + #pattern, ny)\
10935 return\
10936 end\
10937 sx = 1\
10938 end\
10939 actions.error('pattern not found')\
10940 end,\
10941\
10942 find_next = function()\
10943 if searchPattern then\
10944 actions.unmark()\
10945 actions.find(searchPattern, x + 1)\
10946 end\
10947 end,\
10948\
10949 find_prompt = function()\
10950 page.search:show()\
10951 end,\
10952\
10953 quick_open = function(force)\
10954 if not force and undo.chain[#undo.chain] ~= lastSave then\
10955 page.unsaved:show('quick_open')\
10956 else\
10957 page.quick_open:show()\
10958 end\
10959 end,\
10960\
10961 file_open = function(force)\
10962 if not force and undo.chain[#undo.chain] ~= lastSave then\
10963 page.unsaved:show('file_open')\
10964 else\
10965 page.file_open:show()\
10966 end\
10967 end,\
10968\
10969 recent = function(force)\
10970 if not force and undo.chain[#undo.chain] ~= lastSave then\
10971 page.unsaved:show('recent')\
10972 else\
10973 page.recent:show()\
10974 end\
10975 end,\
10976\
10977 file_new = function(force)\
10978 if not force and undo.chain[#undo.chain] ~= lastSave then\
10979 page.unsaved:show('file_new')\
10980 else\
10981 actions.open('/untitled.lua')\
10982 end\
10983 end,\
10984\
10985 open = function(filename)\
10986 if not actions.load(filename) then\
10987 actions.error('unable to load file')\
10988 end\
10989 end,\
10990\
10991 load = function(path)\
10992 if not path or (fs.exists(path) and fs.isDir(path)) then\
10993 return false\
10994 end\
10995 fileInfo = getFileInfo(path)\
10996\
10997 x, y = 1, 1\
10998 scrollX, scrollY = 0, 0\
10999 lastPos = { x = 1, y = 1 }\
11000 lastSave = nil\
11001 dirty = { y = 1, ey = h }\
11002 mark = { }\
11003 undo = { chain = { }, redo = { } }\
11004\
11005 tLines = Util.readLines(fileInfo.path) or { }\
11006 if #tLines == 0 then\
11007 _insert(tLines, '')\
11008 end\
11009\
11010 --[[\
11011 local function detabify(l)\
11012 return l:gsub('\\26\\26', '\\9'):gsub('\\26', '\\9')\
11013 end ]]\
11014\
11015 -- since we can't handle tabs, convert them to spaces :(\
11016 local t1, t2 = ' ', ' '\
11017 local function tabify(l)\
11018 repeat\
11019 local i = l:find('\\9')\
11020 if i then\
11021 local tabs = (i - 1) % 2 == 0 and t2 or t1\
11022 l = l:sub(1, i - 1) .. tabs .. l:sub(i + 1)\
11023 end\
11024 until not i\
11025 return l\
11026 end\
11027\
11028 for k, v in pairs(tLines) do\
11029 tLines[k] = tabify(v)\
11030 end\
11031\
11032 local name = fileInfo.path\
11033 if fileInfo.isNew then\
11034 if not fileInfo.dirExists then\
11035 actions.info('\"%s\" [New DIRECTORY]', name)\
11036 else\
11037 actions.info('\"%s\" [New File]', name)\
11038 end\
11039 elseif fileInfo.isReadOnly then\
11040 actions.info('\"%s\" [readonly] %dL, %dC',\
11041 name, #tLines, fs.getSize(fileInfo.path))\
11042 else\
11043 actions.info('\"%s\" %dL, %dC',\
11044 name, #tLines, fs.getSize(fileInfo.path))\
11045 end\
11046\
11047 return true\
11048 end,\
11049\
11050 save = function(filename)\
11051 filename = filename or fileInfo.path\
11052 if fs.isReadOnly(filename) then\
11053 actions.error(\"access denied\")\
11054 else\
11055 local s, m = pcall(function()\
11056 if not Util.writeLines(filename, tLines) then\
11057 error(\"Failed to open \" .. filename)\
11058 end\
11059 end)\
11060\
11061 if s then\
11062 lastSave = undo.chain[#undo.chain]\
11063 fileInfo = getFileInfo(filename)\
11064 actions.info('\"%s\" %dL, %dC written',\
11065 fileInfo.path, #tLines, fs.getSize(fileInfo.path))\
11066 return true\
11067 else\
11068 actions.error(m)\
11069 end\
11070 end\
11071 end,\
11072\
11073 save_as = function()\
11074 page.save_as:show()\
11075 end,\
11076\
11077 exit = function(force)\
11078 if not force and undo.chain[#undo.chain] ~= lastSave then\
11079 page.unsaved:show('exit')\
11080 else\
11081 UI:quit()\
11082 end\
11083 end,\
11084\
11085 run = function()\
11086 if not multishell then\
11087 actions.error('open available with multishell')\
11088 return\
11089 end\
11090 local routine = {\
11091 focused = true,\
11092 title = fs.getName(fileInfo.path),\
11093 chainExit = function(_, result)\
11094 -- display results of process before closing window\
11095 if result then -- clean exit\
11096 -- any errors will be picked up by multishells\
11097 -- error handling\
11098 print('Press enter to exit')\
11099 while true do\
11100 local e, code = os.pullEventRaw('key')\
11101 if e == 'terminate' or e == 'key' and code == keys.enter then\
11102 break\
11103 end\
11104 end\
11105 end\
11106 end,\
11107 }\
11108 if undo.chain[#undo.chain] == lastSave then\
11109 routine.path = 'sys/apps/shell.lua'\
11110 routine.args = { fileInfo.path }\
11111 else\
11112 local fn, msg = load(_concat(tLines, '\\n'), fileInfo.path)\
11113 if not fn then\
11114 local ln = msg:match(':(%d+):')\
11115 if ln and tonumber(ln) then\
11116 actions.go_to(1, tonumber(ln))\
11117 end\
11118 actions.error(msg)\
11119 return\
11120 end\
11121 routine.fn = fn\
11122 end\
11123 multishell.openTab(_ENV, routine)\
11124 end,\
11125\
11126 status = function()\
11127 local modified = undo.chain[#undo.chain] == lastSave and '' or '[Modified] '\
11128 actions.info('\"%s\" %s%d lines --%d%%--',\
11129 fileInfo.path, modified, #tLines,\
11130 math.floor((y - 1) / (#tLines - 1) * 100))\
11131 end,\
11132\
11133 dirty_line = function(dy)\
11134 if dirty.y == 0 then\
11135 dirty.y = dy\
11136 dirty.ey = dy\
11137 else\
11138 dirty.y = math.min(dirty.y, dy)\
11139 dirty.ey = math.max(dirty.ey, dy)\
11140 end\
11141 end,\
11142\
11143 dirty_range = function(dy, dey)\
11144 actions.dirty_line(dy)\
11145 actions.dirty_line(dey or #tLines)\
11146 end,\
11147\
11148 dirty = function()\
11149 actions.dirty_line(y)\
11150 end,\
11151\
11152 dirty_all = function()\
11153 actions.dirty_line(1)\
11154 actions.dirty_line(#tLines)\
11155 end,\
11156\
11157 mark_begin = function()\
11158 actions.dirty()\
11159 if not mark.active then\
11160 mark.active = true\
11161 mark.anchor = { x = x, y = y }\
11162 end\
11163 end,\
11164\
11165 mark_finish = function()\
11166 if y == mark.anchor.y then\
11167 if x == mark.anchor.x then\
11168 mark.active = false\
11169 else\
11170 mark.x = math.min(mark.anchor.x, x)\
11171 mark.y = y\
11172 mark.ex = math.max(mark.anchor.x, x)\
11173 mark.ey = y\
11174 end\
11175 elseif y < mark.anchor.y then\
11176 mark.x = x\
11177 mark.y = y\
11178 mark.ex = mark.anchor.x\
11179 mark.ey = mark.anchor.y\
11180 else\
11181 mark.x = mark.anchor.x\
11182 mark.y = mark.anchor.y\
11183 mark.ex = x\
11184 mark.ey = y\
11185 end\
11186 actions.dirty()\
11187 mark.continue = mark.active\
11188 end,\
11189\
11190 unmark = function()\
11191 if mark.active then\
11192 actions.dirty_range(mark.y, mark.ey)\
11193 mark.active = false\
11194 end\
11195 end,\
11196\
11197 mark_anchor = function(nx, ny)\
11198 actions.go_to(nx, ny)\
11199 actions.unmark()\
11200 actions.mark_begin()\
11201 actions.mark_finish()\
11202 end,\
11203\
11204 mark_to = function(nx, ny)\
11205 actions.mark_begin()\
11206 actions.go_to(nx, ny)\
11207 actions.mark_finish()\
11208 end,\
11209\
11210 mark_up = function()\
11211 actions.mark_begin()\
11212 actions.up()\
11213 actions.mark_finish()\
11214 end,\
11215\
11216 mark_right = function()\
11217 actions.mark_begin()\
11218 actions.right()\
11219 actions.mark_finish()\
11220 end,\
11221\
11222 mark_down = function()\
11223 actions.mark_begin()\
11224 actions.down()\
11225 actions.mark_finish()\
11226 end,\
11227\
11228 mark_left = function()\
11229 actions.mark_begin()\
11230 actions.left()\
11231 actions.mark_finish()\
11232 end,\
11233\
11234 mark_line = function()\
11235 actions.home()\
11236 actions.mark_begin()\
11237 actions.toend()\
11238 actions.right()\
11239 actions.mark_finish()\
11240 end,\
11241\
11242 mark_word = function()\
11243 actions.mark_begin()\
11244 actions.word()\
11245 actions.mark_finish()\
11246 end,\
11247\
11248 mark_current_word = function(cx, cy)\
11249 local index = 1\
11250 actions.go_to(cx, cy)\
11251 while true do\
11252 local s, e = tLines[y]:find('%w+', index)\
11253 if not s or s - 1 > x then\
11254 break\
11255 end\
11256 if x >= s and x <= e then\
11257 x = s\
11258 actions.mark_begin()\
11259 x = e + 1\
11260 actions.mark_finish()\
11261 x, y = cx, cy\
11262 break\
11263 end\
11264 index = e + 1\
11265 end\
11266 end,\
11267\
11268 mark_backword = function()\
11269 actions.mark_begin()\
11270 actions.backword()\
11271 actions.mark_finish()\
11272 end,\
11273\
11274 mark_home = function()\
11275 actions.mark_begin()\
11276 actions.home()\
11277 actions.mark_finish()\
11278 end,\
11279\
11280 mark_end = function()\
11281 actions.mark_begin()\
11282 actions.toend()\
11283 actions.mark_finish()\
11284 end,\
11285\
11286 mark_all = function()\
11287 mark.anchor = { x = 1, y = 1 }\
11288 mark.active = true\
11289 mark.continue = true\
11290 mark.x = 1\
11291 mark.y = 1\
11292 mark.ey = #tLines\
11293 mark.ex = #tLines[mark.ey] + 1\
11294 actions.dirty_all()\
11295 end,\
11296\
11297 set_cursor = function()\
11298 lastPos.x = x\
11299 lastPos.y = y\
11300\
11301 local screenX = x - scrollX\
11302 local screenY = y - scrollY\
11303\
11304 if screenX < 1 then\
11305 scrollX = math.max(0, x - 4)\
11306 actions.dirty_all()\
11307 elseif screenX > w then\
11308 scrollX = x - w + 3\
11309 actions.dirty_all()\
11310 end\
11311\
11312 if screenY < 1 then\
11313 scrollY = y - 1\
11314 actions.dirty_all()\
11315 elseif screenY > h then\
11316 scrollY = y - h\
11317 actions.dirty_all()\
11318 end\
11319 end,\
11320\
11321 top = function()\
11322 actions.go_to(1, 1)\
11323 end,\
11324\
11325 bottom = function()\
11326 y = #tLines\
11327 x = #tLines[y] + 1\
11328 end,\
11329\
11330 up = function()\
11331 if y > 1 then\
11332 x = math.min(x, #tLines[y - 1] + 1)\
11333 y = y - 1\
11334 end\
11335 end,\
11336\
11337 down = function()\
11338 if y < #tLines then\
11339 x = math.min(x, #tLines[y + 1] + 1)\
11340 y = y + 1\
11341 end\
11342 end,\
11343\
11344 tab = function()\
11345 if mark.active then\
11346 actions.delete()\
11347 end\
11348 actions.insertText(x, y, ' ')\
11349 end,\
11350\
11351 page_up = function()\
11352 actions.go_to(x, y - h)\
11353 end,\
11354\
11355 page_down = function()\
11356 actions.go_to(x, y + h)\
11357 end,\
11358\
11359 home = function()\
11360 x = 1\
11361 end,\
11362\
11363 toend = function()\
11364 x = #tLines[y] + 1\
11365 end,\
11366\
11367 left = function()\
11368 if x > 1 then\
11369 x = x - 1\
11370 elseif y > 1 then\
11371 x = #tLines[y - 1] + 1\
11372 y = y - 1\
11373 else\
11374 return false\
11375 end\
11376 return true\
11377 end,\
11378\
11379 right = function()\
11380 if x < #tLines[y] + 1 then\
11381 x = x + 1\
11382 elseif y < #tLines then\
11383 x = 1\
11384 y = y + 1\
11385 end\
11386 end,\
11387\
11388 word = function()\
11389 local nx = nextWord(tLines[y], x)\
11390 if nx then\
11391 x = nx\
11392 elseif x < #tLines[y] + 1 then\
11393 x = #tLines[y] + 1\
11394 elseif y < #tLines then\
11395 x = 1\
11396 y = y + 1\
11397 end\
11398 end,\
11399\
11400 backword = function()\
11401 if x == 1 then\
11402 actions.left()\
11403 else\
11404 local sLine = tLines[y]\
11405 local lx = 1\
11406 while true do\
11407 local nx = nextWord(sLine, lx)\
11408 if not nx or nx >= x then\
11409 break\
11410 end\
11411 lx = nx\
11412 end\
11413 if not lx then\
11414 x = 1\
11415 else\
11416 x = lx\
11417 end\
11418 end\
11419 end,\
11420\
11421 insertText = function(sx, sy, text)\
11422 x = sx\
11423 y = sy\
11424 local sLine = tLines[y]\
11425\
11426 if not text:find('\\n') then\
11427 tLines[y] = sLine:sub(1, x - 1) .. text .. sLine:sub(x)\
11428 actions.dirty_line(y)\
11429 x = x + #text\
11430 else\
11431 local lines = Util.split(text)\
11432 local remainder = sLine:sub(x)\
11433 tLines[y] = sLine:sub(1, x - 1) .. lines[1]\
11434 actions.dirty_range(y, #tLines + #lines)\
11435 x = x + #lines[1]\
11436 for k = 2, #lines do\
11437 y = y + 1\
11438 _insert(tLines, y, lines[k])\
11439 x = #lines[k] + 1\
11440 end\
11441 tLines[y] = tLines[y]:sub(1, x) .. remainder\
11442 end\
11443\
11444 actions.undo_add(\
11445 { action = 'deleteText', args = { sx, sy, x, y } })\
11446 end,\
11447\
11448 deleteText = function(sx, sy, ex, ey)\
11449 x = sx\
11450 y = sy\
11451\
11452 local text = actions.copyText(sx, sy, ex, ey)\
11453 actions.undo_add(\
11454 { action = 'insertText', args = { sx, sy, text } })\
11455\
11456 local front = tLines[sy]:sub(1, sx - 1)\
11457 local back = tLines[ey]:sub(ex, #tLines[ey])\
11458 for _ = 2, ey - sy + 1 do\
11459 _remove(tLines, y + 1)\
11460 end\
11461 tLines[y] = front .. back\
11462 if sy ~= ey then\
11463 actions.dirty_range(y)\
11464 else\
11465 actions.dirty()\
11466 end\
11467 end,\
11468\
11469 copyText = function(csx, csy, cex, cey)\
11470 local count = 0\
11471 local lines = { }\
11472\
11473 for cy = csy, cey do\
11474 local line = tLines[cy]\
11475 if line then\
11476 local cx = 1\
11477 local ex = #line\
11478 if cy == csy then\
11479 cx = csx\
11480 end\
11481 if cy == cey then\
11482 ex = cex - 1\
11483 end\
11484 local str = line:sub(cx, ex)\
11485 count = count + #str\
11486 _insert(lines, str)\
11487 end\
11488 end\
11489 return _concat(lines, '\\n'), count\
11490 end,\
11491\
11492 delete = function()\
11493 if mark.active then\
11494 actions.deleteText(mark.x, mark.y, mark.ex, mark.ey)\
11495 else\
11496 local nLimit = #tLines[y] + 1\
11497 if x < nLimit then\
11498 actions.deleteText(x, y, x + 1, y)\
11499 elseif y < #tLines then\
11500 actions.deleteText(x, y, 1, y + 1)\
11501 end\
11502 end\
11503 end,\
11504\
11505 backspace = function()\
11506 if mark.active or actions.left() then\
11507 actions.delete()\
11508 end\
11509 end,\
11510\
11511 enter = function()\
11512 local sLine = tLines[y]\
11513 local _,spaces = sLine:find(\"^[ ]+\")\
11514 if not spaces then\
11515 spaces = 0\
11516 end\
11517 spaces = math.min(spaces, x - 1)\
11518 if mark.active then\
11519 actions.delete()\
11520 end\
11521 actions.insertText(x, y, '\\n' .. _rep(' ', spaces))\
11522 end,\
11523\
11524 char = function(ch)\
11525 if mark.active then\
11526 actions.delete()\
11527 end\
11528 actions.insertText(x, y, ch)\
11529 end,\
11530\
11531 copy_marked = function()\
11532 local text = actions.copyText(mark.x, mark.y, mark.ex, mark.ey)\
11533 os.queueEvent('clipboard_copy', text)\
11534 actions.info('shift-^v to paste')\
11535 end,\
11536\
11537 cut = function()\
11538 if mark.active then\
11539 actions.copy_marked()\
11540 actions.delete()\
11541 end\
11542 end,\
11543\
11544 copy = function()\
11545 if mark.active then\
11546 actions.copy_marked()\
11547 mark.continue = true\
11548 end\
11549 end,\
11550\
11551 paste = function(text)\
11552 if mark.active then\
11553 actions.delete()\
11554 end\
11555 if text then\
11556 actions.insertText(x, y, text)\
11557 actions.info('%d chars added', #text)\
11558 else\
11559 actions.info('clipboard empty')\
11560 end\
11561 end,\
11562\
11563 paste_internal = function()\
11564 os.queueEvent('clipboard_paste')\
11565 end,\
11566\
11567 go_to = function(cx, cy)\
11568 y = math.min(math.max(cy, 1), #tLines)\
11569 x = math.min(math.max(cx, 1), #tLines[y] + 1)\
11570 end,\
11571\
11572 scroll_up = function()\
11573 if scrollY > 0 then\
11574 scrollY = scrollY - 1\
11575 actions.dirty_all()\
11576 end\
11577 mark.continue = mark.active\
11578 end,\
11579\
11580 scroll_down = function()\
11581 local nMaxScroll = #tLines - h\
11582 if scrollY < nMaxScroll then\
11583 scrollY = scrollY + 1\
11584 actions.dirty_all()\
11585 end\
11586 mark.continue = mark.active\
11587 end,\
11588\
11589 redraw = function()\
11590 redraw()\
11591 end,\
11592\
11593 process = function(action, ...)\
11594 if not actions[action] then\
11595 error('Invaid action: ' .. action)\
11596 end\
11597\
11598 local wasMarking = mark.continue\
11599 mark.continue = false\
11600\
11601 -- for undo purposes, treat tab and enter as char actions\
11602 local a = (action == 'tab' or action == 'enter') and 'char' or action\
11603 undo.continue = a == undo.lastAction\
11604\
11605 actions[action](...)\
11606\
11607 undo.lastAction = a\
11608\
11609 if x ~= lastPos.x or y ~= lastPos.y then\
11610 actions.set_cursor()\
11611 end\
11612 if not mark.continue and wasMarking then\
11613 actions.unmark()\
11614 end\
11615\
11616 actions.redraw()\
11617 end,\
11618}\
11619\
11620local args = { ... }\
11621local filename = args[1] and shell.resolve(args[1])\
11622if not (actions.load(filename) or actions.load(config.filename) or actions.load('untitled.lua')) then\
11623 error('Error opening file')\
11624end\
11625\
11626UI:setPage(page)\
11627local s, m = pcall(function() UI:start() end)\
11628if not s then\
11629 actions.save('/crash.txt')\
11630 print('Editor has crashed. File saved as /crash.txt')\
11631 error(m, -1)\
11632end",
11633 [ "core/apis/nameDB.lua" ] = "local JSON = require('opus.json')\
11634local TableDB = require('core.tableDB')\
11635local Util = require('opus.util')\
11636\
11637local fs = _G.fs\
11638\
11639local CORE_DIR = '/packages/core/etc/names'\
11640local USER_DIR = '/usr/etc/names'\
11641\
11642local nameDB = TableDB()\
11643\
11644function nameDB:loadDirectory(directory)\
11645 if fs.exists(directory) then\
11646 local files = fs.list(directory)\
11647 table.sort(files)\
11648\
11649 for _,file in ipairs(files) do\
11650 local mod = file:match('(%S+).json')\
11651 if mod then\
11652 local blocks = JSON.decodeFromFile(fs.combine(directory, file))\
11653\
11654 if not blocks then\
11655 error('Unable to read ' .. fs.combine(directory, file))\
11656 end\
11657\
11658 for strId, block in pairs(blocks) do\
11659 strId = string.format('%s:%s', mod, strId)\
11660 if type(block.name) == 'string' then\
11661 self.data[strId .. ':0'] = block.name\
11662 else\
11663 for nid,name in pairs(block.name) do\
11664 self.data[strId .. ':' .. (nid-1)] = name\
11665 end\
11666 end\
11667 end\
11668\
11669 elseif file:match('(%S+).db') then\
11670 local names = Util.readTable(fs.combine(directory, file))\
11671 if not names then\
11672 error('Unable to read ' .. fs.combine(directory, file))\
11673 end\
11674 for key,name in pairs(names) do\
11675 self.data[key] = name\
11676 end\
11677 end\
11678 end\
11679 end\
11680end\
11681\
11682function nameDB:load()\
11683 self:loadDirectory(CORE_DIR)\
11684 self:loadDirectory(USER_DIR)\
11685end\
11686\
11687function nameDB:getName(strId)\
11688 return self.data[strId] or strId\
11689end\
11690\
11691nameDB:load()\
11692\
11693return nameDB",
11694 [ "games/ccTunes.lua" ] = "local Sound = require('opus.sound')\
11695\
11696local os = _G.os\
11697\
11698local tunes = {\
11699 { sound = 'record.11', length = '1:11' },\
11700 { sound = 'record.13', length = '2:58' },\
11701 { sound = 'record.blocks', length = '5:45' },\
11702 { sound = 'record.cat', length = '3:05' },\
11703 { sound = 'record.chirp', length = '3:05' },\
11704 { sound = 'record.far', length = '2:54' },\
11705 { sound = 'record.mall', length = '3:17' },\
11706 { sound = 'record.mellohi', length = '1:36' },\
11707 { sound = 'record.stal', length = '2:30' },\
11708 { sound = 'record.strad', length = '3:08' },\
11709 { sound = 'record.wait', length = '3:58' },\
11710 { sound = 'record.ward', length = '4:11' },\
11711}\
11712\
11713while true do\
11714 local song = tunes[math.random(1, #tunes)]\
11715 Sound.play(song.sound)\
11716 local min, sec = song.length:match('(%d+):(%d+)')\
11717 local length = tonumber(min)*60 + tonumber(sec)\
11718 print(string.format('Playing %s (%s)', song.sound, song.length))\
11719 os.sleep(length + 3)\
11720end",
11721 [ "minify/.package" ] = "{\
11722 title = 'Lua minification',\
11723 repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/minify',\
11724 description = [[ Minifies Lua files ]],\
11725 license = 'MIT',\
11726}",
11727 [ "core/apis/message.lua" ] = "local Event = require('opus.event')\
11728\
11729local Message = { }\
11730\
11731local messageHandlers = {}\
11732\
11733function Message.enable()\
11734 if not device.wireless_modem.isOpen(os.getComputerID()) then\
11735 device.wireless_modem.open(os.getComputerID())\
11736 end\
11737 if not device.wireless_modem.isOpen(60000) then\
11738 device.wireless_modem.open(60000)\
11739 end\
11740end\
11741\
11742if device and device.wireless_modem then\
11743 Message.enable()\
11744end\
11745\
11746Event.on('device_attach', function(event, deviceName)\
11747 if deviceName == 'wireless_modem' then\
11748 Message.enable()\
11749 end\
11750end)\
11751\
11752function Message.addHandler(type, f)\
11753 table.insert(messageHandlers, {\
11754 type = type,\
11755 f = f,\
11756 enabled = true\
11757 })\
11758end\
11759\
11760function Message.removeHandler(h)\
11761 for k,v in pairs(messageHandlers) do\
11762 if v == h then\
11763 messageHandlers[k] = nil\
11764 break\
11765 end\
11766 end\
11767end\
11768\
11769Event.on('modem_message',\
11770 function(event, side, sendChannel, replyChannel, msg, distance)\
11771 if msg and msg.type then -- filter out messages from other systems\
11772 local id = replyChannel\
11773 for k,h in pairs(messageHandlers) do\
11774 if h.type == msg.type then\
11775-- should provide msg.contents instead of message - type is already known\
11776 h.f(h, id, msg, distance)\
11777 end\
11778 end\
11779 end\
11780 end\
11781)\
11782\
11783function Message.send(id, msgType, contents)\
11784 if not device.wireless_modem then\
11785 error('No modem attached', 2)\
11786 end\
11787\
11788 if id then\
11789 device.wireless_modem.transmit(id, os.getComputerID(), {\
11790 type = msgType, contents = contents\
11791 })\
11792 else\
11793 device.wireless_modem.transmit(60000, os.getComputerID(), {\
11794 type = msgType, contents = contents\
11795 })\
11796 end\
11797end\
11798\
11799function Message.broadcast(t, contents)\
11800 if not device.wireless_modem then\
11801 error('No modem attached', 2)\
11802 end\
11803\
11804 Message.send(nil, t, contents)\
11805end\
11806\
11807function Message.waitForMessage(msgType, timeout, fromId)\
11808 local timerId = os.startTimer(timeout)\
11809 repeat\
11810 local e, side, _id, id, msg, distance = os.pullEvent()\
11811 if e == 'modem_message' then\
11812 if msg and msg.type and msg.type == msgType then\
11813 if not fromId or id == fromId then\
11814 return e, id, msg, distance\
11815 end\
11816 end\
11817 end\
11818 until e == 'timer' and side == timerId\
11819end\
11820\
11821return Message",
11822 [ "common/etc/scripts/shutdown" ] = "os.shutdown()",
11823 [ "common/debugMonitor.lua" ] = "local Util = require('opus.util')\
11824\
11825local device = _G.device\
11826local os = _G.os\
11827local peripheral = _G.peripheral\
11828local term = _G.term\
11829\
11830local args = { ... }\
11831local mon = not args[1] and term.current() or\
11832 device[args[1]] or\
11833 peripheral.wrap(args[1]) or\
11834 peripheral.find('monitor') or\
11835 error('Syntax: debug <monitor>')\
11836\
11837mon.clear()\
11838if mon.setTextScale then\
11839 mon.setTextScale(.5)\
11840end\
11841mon.setCursorPos(1, 1)\
11842\
11843local oldDebug = _G._syslog\
11844\
11845_G._syslog = function(...)\
11846 local oldTerm = term.redirect(mon)\
11847 Util.print(...)\
11848 term.redirect(oldTerm)\
11849 oldDebug(...)\
11850end\
11851\
11852repeat\
11853 local e, side = os.pullEventRaw('monitor_touch')\
11854 if e == 'monitor_touch' and side == mon.side then\
11855 mon.clear()\
11856 if mon.setTextScale then\
11857 mon.setTextScale(.5)\
11858 end\
11859 mon.setCursorPos(1, 1)\
11860 end\
11861until e == 'terminate'\
11862\
11863_G._syslog = oldDebug",
11864 [ "core/apis/proxy.lua" ] = "local Socket = require('opus.socket')\
11865\
11866local Proxy = { }\
11867\
11868function Proxy.create(remoteId, uri)\
11869 local socket, msg = Socket.connect(remoteId, 188)\
11870\
11871 if not socket then\
11872 error(msg)\
11873 end\
11874\
11875 socket.co = coroutine.running()\
11876\
11877 socket:write(uri)\
11878 local methods = socket:read(2) or error('Timed out')\
11879\
11880 local hijack = { }\
11881 for _,method in pairs(methods) do\
11882 hijack[method] = function(...)\
11883 socket:write({ method, ... })\
11884 local resp = socket:read()\
11885 if not resp then\
11886 error('timed out: ' .. method)\
11887 end\
11888 return table.unpack(resp)\
11889 end\
11890 end\
11891\
11892 return hijack, socket\
11893end\
11894\
11895return Proxy",
11896 [ "common/etc/scripts/setHome" ] = "local Config = require('opus.config')\
11897local pt = turtle.enableGPS()\
11898if pt then\
11899 local config = Config.load('gps', { })\
11900 config.home = pt\
11901 Config.update('gps', config)\
11902end",
11903 [ "core/apis/meAdapter.lua" ] = "local class = require('opus.class')\
11904local itemDB = require('core.itemDB')\
11905local Peripheral = require('opus.peripheral')\
11906local Util = require('opus.util')\
11907\
11908local os = _G.os\
11909\
11910local convertNames = {\
11911 name = 'id',\
11912 damage = 'dmg',\
11913 maxCount = 'max_size',\
11914 count = 'qty',\
11915 displayName = 'display_name',\
11916 maxDamage = 'max_dmg',\
11917 nbtHash = 'nbt_hash',\
11918}\
11919\
11920-- Strip off color prefix\
11921local function safeString(text)\
11922\
11923 local val = text:byte(1)\
11924\
11925 if val < 32 or val > 128 then\
11926\
11927 local newText = {}\
11928 for i = 4, #text do\
11929 val = text:byte(i)\
11930 newText[i - 3] = (val > 31 and val < 127) and val or 63\
11931 end\
11932 return string.char(unpack(newText))\
11933 end\
11934\
11935 return text\
11936end\
11937\
11938local function convertItem(item)\
11939 for k,v in pairs(convertNames) do\
11940 item[k] = item[v]\
11941 item[v] = nil\
11942 end\
11943 item.displayName = safeString(item.displayName)\
11944end\
11945\
11946local MEAdapter = class()\
11947\
11948function MEAdapter:init(args)\
11949 local defaults = {\
11950 items = { },\
11951 name = 'ME',\
11952 jobList = { },\
11953 }\
11954 Util.merge(self, defaults)\
11955 Util.merge(self, args)\
11956\
11957 local chest\
11958\
11959 if not self.side then\
11960 chest = Peripheral.getByMethod('getAvailableItems')\
11961 else\
11962 chest = Peripheral.getBySide(self.side)\
11963 if chest and not chest.getAvailableItems then\
11964 chest = nil\
11965 end\
11966 end\
11967\
11968 if chest then\
11969 Util.merge(self, chest)\
11970 end\
11971end\
11972\
11973function MEAdapter:isValid()\
11974 return self.getAvailableItems and self.getAvailableItems()\
11975end\
11976\
11977function MEAdapter:refresh()\
11978 self.items = nil\
11979 local hasItems, failed\
11980\
11981 local s, m = pcall(function()\
11982 self.items = self.getAvailableItems('all')\
11983 for _,v in pairs(self.items) do\
11984 Util.merge(v, v.item)\
11985 convertItem(v)\
11986\
11987 -- if power has been interrupted, the list will still be returned\
11988 -- but all items will have a 0 quantity\
11989 -- ensure that the list is valid\
11990 if not hasItems then\
11991 hasItems = v.count > 0\
11992 end\
11993\
11994 if not v.fingerprint then\
11995 failed = true\
11996 break\
11997 end\
11998\
11999 if not itemDB:get(v) then\
12000 itemDB:add(v, v)\
12001 end\
12002 end\
12003 end)\
12004 itemDB:flush()\
12005\
12006 if not s and m then\
12007 _G._syslog(m)\
12008 end\
12009\
12010 if s and not failed and hasItems and self.items and not Util.empty(self.items) then\
12011 return self.items\
12012 end\
12013 self.items = nil\
12014end\
12015\
12016function MEAdapter:listItems()\
12017 self:refresh()\
12018 return self.items\
12019end\
12020\
12021function MEAdapter:getItemInfo(item)\
12022 for _,i in pairs(self.items) do\
12023 if item.name == i.name and\
12024 item.damage == i.damage and\
12025 item.nbtHash == i.nbtHash then\
12026 return i\
12027 end\
12028 end\
12029end\
12030\
12031function MEAdapter:isCPUAvailable()\
12032 local cpus = self.getCraftingCPUs() or { }\
12033 local available = false\
12034\
12035 for cpu,v in pairs(cpus) do\
12036 if not v.busy then\
12037 available = true\
12038 elseif not self.jobList[cpu] then -- something else is crafting something (don't know what)\
12039 return false -- return false since we are in an unknown state\
12040 end\
12041 end\
12042 return available\
12043end\
12044\
12045function MEAdapter:craft(item, count)\
12046 if not self:isCPUAvailable() then\
12047 return false\
12048 end\
12049\
12050 self:refresh()\
12051\
12052 item = self:getItemInfo(item)\
12053 if item and item.is_craftable then\
12054\
12055 local cpus = self.getCraftingCPUs() or { }\
12056 for cpu,v in pairs(cpus) do\
12057 if not v.busy then\
12058 self.requestCrafting({\
12059 id = item.name,\
12060 dmg = item.damage,\
12061 nbt_hash = item.nbtHash,\
12062 },\
12063 count or 1,\
12064 v.name -- CPUs must be named ! use anvil\
12065 )\
12066\
12067 os.sleep(0) -- needed ?\
12068 cpus = self.getCraftingCPUs() or { }\
12069\
12070 if cpus[cpu].busy then\
12071 self.jobList[cpu] = {\
12072 name = item.name,\
12073 damage = item.damage,\
12074 nbtHash = item.nbtHash,\
12075 count = count,\
12076 }\
12077 return true\
12078 end\
12079 break -- only need to try the first available cpu\
12080 end\
12081 end\
12082 return false\
12083 end\
12084end\
12085\
12086function MEAdapter:getJobList()\
12087 local cpus = self.getCraftingCPUs() or { }\
12088 for cpu,v in pairs(cpus) do\
12089 if not v.busy then\
12090 self.jobList[cpu] = nil\
12091 end\
12092 end\
12093\
12094 return self.jobList\
12095end\
12096\
12097function MEAdapter:isCrafting(item)\
12098 for _,v in pairs(self:getJobList()) do\
12099 if v.name == item.name and\
12100 v.damage == item.damage and\
12101 v.nbtHash == item.nbtHash then\
12102 return true\
12103 end\
12104 end\
12105end\
12106\
12107function MEAdapter:craftItems(items)\
12108 local cpus = self.getCraftingCPUs() or { }\
12109 local count = 0\
12110\
12111 for _,cpu in pairs(cpus) do\
12112 if cpu.busy then\
12113 return\
12114 end\
12115 end\
12116\
12117 for _,item in pairs(items) do\
12118 if count >= #cpus then\
12119 break\
12120 end\
12121 if not self:isCrafting(item) then\
12122 if self:craft(item, item.count) then\
12123 count = count + 1\
12124 end\
12125 end\
12126 end\
12127end\
12128\
12129function MEAdapter:provide(item, qty, slot, direction)\
12130 return pcall(function()\
12131 for _,stack in pairs(self.getAvailableItems('all')) do\
12132 if stack.item.id == item.name and\
12133 (not item.damage or stack.item.dmg == item.damage) and\
12134 (not item.nbtHash or stack.item.nbt_hash == item.nbtHash) then\
12135 local amount = math.min(qty, stack.item.qty)\
12136 if amount > 0 then\
12137 self.exportItem(stack.item, direction or self.direction, amount, slot)\
12138 end\
12139 qty = qty - amount\
12140 if qty <= 0 then\
12141 break\
12142 end\
12143 end\
12144 end\
12145 end)\
12146end\
12147\
12148function MEAdapter:insert(slot, qty, toSlot)\
12149 local s, m = pcall(self.pullItem, self.direction, slot, qty, toSlot)\
12150 if not s and m then\
12151 os.sleep(1)\
12152 pcall(self.pullItem, self.direction, slot, qty, toSlot)\
12153 end\
12154end\
12155\
12156return MEAdapter",
12157 [ "common/autorun/common.lua" ] = "local c = function(shell, nIndex, sText)\
12158 if nIndex == 1 then\
12159 return _G.fs.complete(sText, shell.dir(), true, false)\
12160 end\
12161end\
12162\
12163_ENV.shell.setCompletionFunction(\"packages/common/edit.lua\", c)\
12164_ENV.shell.setCompletionFunction(\"packages/common/hexedit.lua\", c)",
12165 [ "common/etc/scripts/reboot" ] = "os.reboot()",
12166 [ "core/apis/itemDB.lua" ] = "local Map = require('opus.map')\
12167local nameDB = require('core.nameDB')\
12168local TableDB = require('core.tableDB')\
12169local Util = require('opus.util')\
12170\
12171local itemDB = TableDB({ fileName = 'usr/config/items.db' })\
12172\
12173local function safeString(text)\
12174 local val = text:byte(1)\
12175\
12176 if val < 32 or val > 128 then\
12177\
12178 local newText = { }\
12179 local skip = 0\
12180 for i = 1, #text do\
12181 val = text:byte(i)\
12182 if val == 167 then\
12183 skip = 2\
12184 end\
12185 if skip > 0 then\
12186 skip = skip - 1\
12187 else\
12188 if val >= 32 and val <= 128 then\
12189 newText[#newText + 1] = val\
12190 end\
12191 end\
12192 end\
12193 return string.char(unpack(newText))\
12194 end\
12195\
12196 return text\
12197end\
12198\
12199function itemDB:makeKey(item)\
12200 if not item then error('itemDB:makeKey: item is required', 2) end\
12201 return table.concat({ item.name, item.damage or '*', item.nbtHash }, ':')\
12202end\
12203\
12204function itemDB:splitKey(key, item)\
12205 item = item or { }\
12206\
12207 local t = Util.split(key, '(.-):')\
12208 if #t[#t] > 8 then\
12209 item.nbtHash = table.remove(t)\
12210 end\
12211 local damage = table.remove(t)\
12212 if damage ~= '*' then\
12213 item.damage = tonumber(damage)\
12214 end\
12215 item.name = table.concat(t, ':')\
12216\
12217 return item\
12218end\
12219\
12220function itemDB:get(key, populateFn)\
12221 if not key then error('itemDB:get: key is required', 2) end\
12222 if type(key) == 'string' then\
12223 key = self:splitKey(key)\
12224 else\
12225 key = Util.shallowCopy(key)\
12226 end\
12227\
12228 local item = self:_get(key)\
12229 if not item and populateFn then\
12230 item = populateFn()\
12231 if item then\
12232 item = self:add(item)\
12233 end\
12234 end\
12235\
12236 return item and Util.merge(key, item)\
12237end\
12238\
12239function itemDB:_get(key)\
12240 if not key then error('itemDB:get: key is required', 2) end\
12241 if type(key) == 'string' then\
12242 key = self:splitKey(key)\
12243 end\
12244\
12245 local item = TableDB.get(self, self:makeKey(key))\
12246 if item then\
12247 return item\
12248 end\
12249\
12250 -- try finding an item that has damage values\
12251 if type(key.damage) == 'number' then\
12252 item = TableDB.get(self, self:makeKey({ name = key.name, nbtHash = key.nbtHash }))\
12253 if item and item.maxDamage then\
12254 item = Util.shallowCopy(item)\
12255 item.damage = key.damage\
12256 if item.maxDamage > 0 and type(item.damage) == 'number' and item.damage > 0 then\
12257 item.displayName = string.format('%s (damage: %s)', item.displayName, item.damage)\
12258 end\
12259 return item\
12260 end\
12261 else\
12262 for k,item in pairs(self.data) do\
12263 if key.name == item.name and\
12264 key.nbtHash == key.nbtHash and\
12265 item.maxDamage > 0 then\
12266 item = Util.shallowCopy(item)\
12267 item.nbtHash = key.nbtHash\
12268 return item\
12269 end\
12270 end\
12271 end\
12272\
12273 if key.nbtHash then\
12274 item = self:get({ name = key.name, damage = key.damage })\
12275\
12276 if item and item.ignoreNBT then\
12277 item = Util.shallowCopy(item)\
12278 item.nbtHash = key.nbtHash\
12279 item.damage = key.damage\
12280 return item\
12281 end\
12282 end\
12283end\
12284\
12285local function formatTime(t)\
12286 local m = math.floor(t/60)\
12287 local s = t % 60\
12288 if s < 10 then\
12289 s = '0' .. s\
12290 end\
12291\
12292 return m .. ':' .. s\
12293end\
12294\
12295--[[\
12296 If the base item contains an NBT hash, then the NBT hash uniquely\
12297 identifies this item.\
12298]]--\
12299function itemDB:add(baseItem)\
12300 local nItem = {\
12301 name = baseItem.name,\
12302 damage = baseItem.damage,\
12303 nbtHash = baseItem.nbtHash,\
12304 }\
12305-- if detail.maxDamage > 0 then\
12306-- nItem.damage = '*'\
12307-- end\
12308\
12309 nItem.displayName = safeString(baseItem.displayName)\
12310 nItem.maxCount = baseItem.maxCount\
12311 nItem.maxDamage = baseItem.maxDamage\
12312\
12313 -- enchanted items\
12314 if baseItem.enchantments then\
12315 if nItem.name == 'minecraft:enchanted_book' then\
12316 nItem.displayName = 'Book: '\
12317 else\
12318 nItem.displayName = nItem.displayName .. ': '\
12319 end\
12320 for k, v in ipairs(baseItem.enchantments) do\
12321 if k > 1 then\
12322 nItem.displayName = nItem.displayName .. ', '\
12323 end\
12324 nItem.displayName = nItem.displayName .. v.fullName\
12325 end\
12326\
12327 -- turtles / computers / etc\
12328 elseif baseItem.computer then\
12329 -- a turtle's NBT is updated constantly\
12330 -- update the cache with the new NBT\
12331 if baseItem.computer.id then\
12332 Map.removeMatches(self.data, { name = nItem.name, displayName = nItem.displayName })\
12333 end\
12334 nItem.displayName = baseItem.computer.label or baseItem.displayName\
12335\
12336 -- disks\
12337 elseif baseItem.media then\
12338 -- don't ignore nbt... as disks can be labeled\
12339 if baseItem.media.recordTitle then\
12340 nItem.displayName = nItem.displayName .. ': ' .. baseItem.media.recordTitle\
12341 end\
12342\
12343 -- potions\
12344 elseif nItem.name == 'minecraft:potion' or nItem.name == 'minecraft:lingering_potion' then\
12345 if baseItem.effects then\
12346 local effect = baseItem.effects[1]\
12347 if effect.amplifier == 1 then\
12348 nItem.displayName = nItem.displayName .. ' II'\
12349 end\
12350 if effect.duration and effect.duration > 0 then\
12351 nItem.displayName = string.format('%s (%s)', nItem.displayName, formatTime(effect.duration))\
12352 end\
12353 end\
12354\
12355 else\
12356 for k,item in pairs(self.data) do\
12357 if nItem.name == item.name and\
12358 nItem.displayName == item.displayName then\
12359\
12360 if nItem.nbtHash ~= item.nbtHash and nItem.damage ~= item.damage then\
12361 nItem.damage = '*'\
12362 nItem.nbtHash = nil\
12363 nItem.ignoreNBT = true\
12364 self.data[k] = nil\
12365 break\
12366 elseif nItem.damage ~= item.damage then\
12367 nItem.damage = '*'\
12368 self.data[k] = nil\
12369 break\
12370 elseif nItem.nbtHash ~= item.nbtHash then\
12371 nItem.nbtHash = nil\
12372 nItem.ignoreNBT = true\
12373 self.data[k] = nil\
12374 break\
12375 end\
12376 end\
12377 end\
12378 end\
12379\
12380 TableDB.add(self, self:makeKey(nItem), nItem)\
12381 nItem = Util.shallowCopy(nItem)\
12382 nItem.damage = baseItem.damage\
12383 nItem.nbtHash = baseItem.nbtHash\
12384\
12385 return nItem\
12386end\
12387\
12388-- Accepts: \"minecraft:stick:0\" or { name = 'minecraft:stick', damage = 0 }\
12389function itemDB:getName(item)\
12390 if type(item) == 'string' then\
12391 item = self:splitKey(item)\
12392 end\
12393\
12394 local detail = self:get(item)\
12395 if detail then\
12396 return detail.displayName\
12397 end\
12398\
12399 -- fallback to nameDB\
12400 local strId = self:makeKey(item)\
12401 local name = nameDB.data[strId]\
12402 if not name and not item.damage then\
12403 name = nameDB.data[self:makeKey({ name = item.name, damage = 0, nbtHash = item.nbtHash })]\
12404 end\
12405 return name or strId\
12406end\
12407\
12408function itemDB:getMaxCount(item)\
12409 local detail = self:get(item)\
12410 return detail and detail.maxCount or 64\
12411end\
12412\
12413function itemDB:load()\
12414 TableDB.load(self)\
12415\
12416 for key,item in pairs(self.data) do\
12417 self:splitKey(key, item)\
12418 item.maxDamage = item.maxDamage or 0\
12419 item.maxCount = item.maxCount or 64\
12420 end\
12421end\
12422\
12423function itemDB:flush()\
12424 if self.dirty then\
12425\
12426 local t = { }\
12427 for k,v in pairs(self.data) do\
12428 v = Util.shallowCopy(v)\
12429 v.name = nil\
12430 v.damage = nil\
12431 v.nbtHash = nil\
12432v.count = nil -- wipe out previously saved counts - temporary\
12433 if v.maxDamage == 0 then\
12434 v.maxDamage = nil\
12435 end\
12436 if v.maxCount == 64 then\
12437 v.maxCount = nil\
12438 end\
12439 t[k] = v\
12440 end\
12441\
12442 Util.writeTable(self.fileName, t)\
12443 self.dirty = false\
12444 end\
12445end\
12446\
12447itemDB:load()\
12448\
12449return itemDB",
12450 [ "common/etc/apps.db" ] = "{\
12451 [ \"c5497bca58468ae64aed6c0fd921109217988db3\" ] = {\
12452 title = \"Events\",\
12453 category = \"System\",\
12454 iconExt = \"\\0300\\031 \\159\\135\\030 \\0310\\156\\0301\\031 \\159\\030 \\0311\\144\\0300\\031 \\147\\139\\030 \\0310\\144\\010\\0300\\128\\128\\030 \\149\\0311\\157\\142\\0300\\031 \\149\\0310\\128\\128\\010\\030 \\130\\139\\141\\0311\\130\\131\\0310\\142\\135\\129\",\
12455 run = \"Events.lua\",\
12456 },\
12457 [ \"f1e14c30480042e8c71b5482d92ef161815b915e\" ] = {\
12458 title = \"DiskCopy\",\
12459 category = \"System\",\
12460 run = \"DiskCopy\",\
12461 iconExt = \"\\0305\\0318\\138\\0303\\159\\0305\\0317\\133\\030 \\0315\\148\\031 \\128\\0318\\151\\129\\010\\0300\\0315\\149\\030b\\0318\\138\\143\\0317\\133\\030 \\031b\\148\\0308\\031 \\140\\030 \\0318\\145\\010\\030 \\031 \\128\\0300\\031b\\149\\0310\\128\\128\\030 \\031b\\149\\0318\\157\\133\",\
12462 },\
12463 [ \"7ef35cac539f84722b0a988caee03b2df734c56a\" ] = {\
12464 title = \"AppStore\",\
12465 category = \"System\",\
12466 icon = \"\\030 \\0310=\\0300 \\030 XX\\0300\\031f \\030 \\010\\030 \\031f \\0300 \\030 \\010\\030 \\031f \\0310o \\031f \\0310o\\031f \",\
12467 iconExt = \"\\030 \\031e\\139\\0318\\151\\151\\151\\151\\151\\149\\010\\030 \\031 \\128\\0308\\136\\136\\136\\136\\030 \\0318\\135\\031 \\128\\010\\030 \\031 \\128\\0317\\130\\031 \\128\\0317\\130\\031 \\128\\128\\128\",\
12468 run = \"Appstore.lua\",\
12469 },\
12470 [ \"90ef98d4b6fd15466f0a1f212ec1db8d9ebe018c\" ] = {\
12471 title = \"Turtles\",\
12472 category = \"Apps\",\
12473 icon = \" \\0305 \\030c \\0305 \\030 \\010\\030d \\030c \\0305 \\030c \\0308 \\030d\\031f\\\"\\010 \\0308\\031f.\\030 \\031 \\0308\\031f.\\030 \\031 \",\
12474 iconExt = \"\\030 \\031 \\128\\0305\\135\\030c\\031c\\128\\128\\0305\\031 \\139\\0307\\149\\0308\\0317\\143\\0307\\128\\010\\030c\\031 \\145\\031c\\128\\030d\\132\\136\\030c\\128\\0307\\149\\0318\\143\\133\\010\\030 \\031 \\128\\0317\\143\\031 \\128\\128\\0317\\143\\031 \\128\\128\\128\",\
12475 run = \"Turtles.lua\",\
12476 },\
12477 [ \"66587a7a62a90fc91c4c88f1872bedaa52d23a35\" ] = {\
12478 title = \"Follow\",\
12479 category = \"Turtle\",\
12480 run = \"Follow.lua\",\
12481 iconExt = \"\\030 \\031 \\128\\128\\128\\128\\128\\128\\0314\\144\\031 \\128\\010\\030 \\031 \\128\\128\\128\\128\\0304\\139\\133\\0310\\130\\030 \\0311\\156\\010\\0307\\0318\\136\\030 \\0317\\149\\0307\\0318\\136\\030 \\0317\\149\\0304\\031 \\144\\0314\\128\\030 \\159\\031 \\128\",\
12482 },\
12483 [ \"df485c871329671f46570634d63216761441bcd6\" ] = {\
12484 title = \"Devices\",\
12485 category = \"System\",\
12486 icon = \"\\0304 \\030 \\010\\030f \\0304 \\0307 \\030 \\031 \\031f_\\010\\030f \\0304 \\0307 \\030 \\031f/\",\
12487 iconExt = \"\\030 \\031 \\128\\128\\128\\0308\\159\\143\\0300\\0317\\151\\0307\\0310\\140\\148\\010\\0314\\151\\131\\0304\\031f\\148\\030 \\0318\\138\\148\\0307\\0310\\138\\131\\129\\010\\0304\\031f\\138\\143\\133\\030 \\0318\\131\\129\\031 \\128\\128\\128\",\
12488 run = \"Devices.lua\",\
12489 },\
12490 [ \"114edfc04a1ab03541bdc80ce064f66a7cfcedbb\" ] = {\
12491 title = \"Recorder\",\
12492 category = \"Apps\",\
12493 icon = \"\\030 \\031f \\031b \\031foo \\010\\030 \\031f \\030e\\031b \\030 \\031f/\\010\\030 \\031b \\030e \\030 \\031f\\\\\",\
12494 iconExt = \"\\030 \\031 \\128\\030e\\143\\030 \\031e\\144\\031 \\128\\0304\\149\\0307\\0314\\131\\131\\030 \\149\\010\\030e\\031 \\129\\031e\\128\\128\\030 \\148\\0304\\031 \\149\\0307\\0318\\140\\140\\030 \\0314\\149\\010\\030 \\031e\\139\\030e\\128\\030 \\159\\129\\0314\\130\\131\\131\\129\",\
12495 run = \"recorder.lua\",\
12496 },\
12497 [ \"5c7e3aec1f9179ce3325a4f7a101dca65ac905f3\" ] = {\
12498 title = \"Swarm\",\
12499 category = \"Turtle\",\
12500 requires = \"neuralInterface\",\
12501 icon = \"\\030 \\0315\\\\\\030 \\031 \\010\\030 \\0304\\031f _ \\030 \\031c/\\0315\\\\\\010\\030 \\0304 \",\
12502 run = \"multiMiner.lua\",\
12503 },\
12504 [ \"783ecb3650eabd68f3caadc387abd23018132967\" ] = {\
12505 title = \"HexEdit\",\
12506 category = \"Apps\",\
12507 requires = \"advancedComputer\",\
12508 iconExt = \"\\030 \\031 \\128\\030d\\159\\030 \\031d\\140\\030d\\031 \\155\\030 \\0315\\140\\0305\\031 \\155\\030 \\128\\010\\030 \\031d\\136\\145\\0315\\136\\145\\031d\\153\\031 \\128\\0315\\153\\010\\030 \\031 \\128\\031d\\130\\140\\134\\0315\\140\\134\\031 \\128\",\
12509 run = \"fileui --exec=hexedit.lua --title=hexedit\",\
12510 },\
12511 [ \"fb1c39e9f4f3c2628ad173ab401a6e4e4baf783d\" ] = {\
12512 title = \"Sounds\",\
12513 category = \"System\",\
12514 run = \"SoundPlayer\",\
12515 iconExt = \"\\030 \\031 \\128\\0307\\159\\129\\030 \\0317\\149\\0310\\144\\0300\\031 \\155\\030 \\0310\\137\\144\\010\\0307\\0317\\128\\128\\128\\030 \\149\\0300\\031 \\149\\030 \\128\\0310\\149\\0300\\031 \\149\\010\\030 \\031 \\128\\0317\\130\\0307\\031 \\144\\030 \\0317\\149\\0310\\129\\134\\152\\129\",\
12516 },\
12517 [ \"464c4ffd019e1e9691dcf0537c797353ef2b1c1d4833d3d463e5b74ae4547344\" ] = {\
12518 title = \"Editor\",\
12519 category = \"Apps\",\
12520 run = \"edit\",\
12521 iconExt = \"7\\\
12522¨¨¨¨f0¨\\\
12523¨¨f0¨¨f\",\
12524 },\
12525 [ \"3f00927a719345edd4a8316599d3b328857987547f8884306861161ffa09647e\" ] = {\
12526 title = \"Write\",\
12527 category = \"Apps\",\
12528 run = \"write\",\
12529 },\
12530}",
12531 [ "core/apis/inventoryAdapter.lua" ] = "local Adapter = { }\
12532\
12533function Adapter.wrap(args)\
12534 local adapters = {\
12535 'core.refinedAdapter',\
12536 'core.meAdapter18',\
12537 'core.chestAdapter18',\
12538\
12539 -- adapters for version 1.7\
12540 'core.meAdapter',\
12541 'core.chestAdapter',\
12542 }\
12543\
12544 for _,adapterType in ipairs(adapters) do\
12545 local adapter = require(adapterType)(args)\
12546\
12547 if adapter:isValid() then\
12548\
12549 -- figure out which direction to push/pull items from an inventory\
12550 -- based on the side the inventory is attached and which way the\
12551 -- turtle/computer is facing\
12552 if args and args.facing and adapter.side and not adapter.direction then\
12553 local horz = { top = 'down', bottom = 'up' }\
12554 adapter.direction = horz[adapter.side]\
12555\
12556 if not adapter.direction then\
12557 local sides = {\
12558 front = 0,\
12559 right = 1,\
12560 back = 2,\
12561 left = 3,\
12562 }\
12563 -- pretty sure computer/turtle have sides reversed\
12564 local cards = {\
12565 east = 0,\
12566 south = 1,\
12567 west = 2,\
12568 north = 3,\
12569 }\
12570 local icards = {\
12571 [ 0 ] = 'west',\
12572 [ 1 ] = 'north',\
12573 [ 2 ] = 'east',\
12574 [ 3 ] = 'south',\
12575 }\
12576 adapter.direction = icards[(cards[args.facing] + sides[adapter.side]) % 4]\
12577 end\
12578 end\
12579 return adapter\
12580 end\
12581 end\
12582end\
12583\
12584return Adapter",
12585 [ "common/etc/scripts/goHome" ] = "local config = require('opus.config').load('gps')\
12586if config.home then\
12587 if turtle.enableGPS() then\
12588 return turtle.pathfind(config.home)\
12589 end\
12590end",
12591 [ "core/apis/tableDB.lua" ] = "local class = require('opus.class')\
12592local Util = require('opus.util')\
12593\
12594local TableDB = class()\
12595function TableDB:init(args)\
12596 local defaults = {\
12597 fileName = '',\
12598 dirty = false,\
12599 data = { },\
12600 }\
12601 Util.merge(defaults, args)\
12602 Util.merge(self, defaults)\
12603end\
12604\
12605function TableDB:load()\
12606 local t = Util.readTable(self.fileName)\
12607 if t then\
12608 self.data = t.data or t\
12609 end\
12610end\
12611\
12612function TableDB:add(key, entry)\
12613 if type(key) == 'table' then\
12614 key = table.concat(key, ':')\
12615 end\
12616 self.data[key] = entry\
12617 self.dirty = true\
12618end\
12619\
12620function TableDB:get(key)\
12621 if type(key) == 'table' then\
12622 key = table.concat(key, ':')\
12623 end\
12624 return self.data[key]\
12625end\
12626\
12627function TableDB:remove(key)\
12628 self.data[key] = nil\
12629 self.dirty = true\
12630end\
12631\
12632function TableDB:flush()\
12633 if self.dirty then\
12634 Util.writeTable(self.fileName, self.data)\
12635 self.dirty = false\
12636 end\
12637end\
12638\
12639return TableDB",
12640 [ "common/Devices.lua" ] = "local Ansi = require('opus.ansi')\
12641local Event = require('opus.event')\
12642local UI = require('opus.ui')\
12643local Util = require('opus.util')\
12644\
12645local device = _G.device\
12646\
12647--[[ -- PeripheralsPage -- ]] --\
12648local peripheralsPage = UI.Page {\
12649 grid = UI.ScrollingGrid {\
12650 ey = -2,\
12651 columns = {\
12652 --{ heading = 'Name', key = 'name' },\
12653 { heading = 'Type', key = 'type' },\
12654 { heading = 'Side', key = 'side' },\
12655 },\
12656 sortColumn = 'type',\
12657 autospace = true,\
12658 enable = function(self)\
12659 Util.clear(self.values)\
12660 for _,v in pairs(device) do\
12661 table.insert(self.values, {\
12662 type = v.type,\
12663 side = v.side,\
12664 name = v.name,\
12665 })\
12666 end\
12667 self:update()\
12668 self:adjustWidth()\
12669 UI.Grid.enable(self)\
12670 end,\
12671 },\
12672 statusBar = UI.StatusBar {\
12673 values = 'Select peripheral',\
12674 },\
12675 accelerators = {\
12676 [ 'control-q' ] = 'quit',\
12677 },\
12678 updatePeripherals = function(self)\
12679 if UI:getCurrentPage() == self then\
12680 self.grid:draw()\
12681 self:sync()\
12682 end\
12683 end,\
12684 eventHandler = function(self, event)\
12685 if event.type == 'quit' then\
12686 UI:quit()\
12687\
12688 elseif event.type == 'grid_select' then\
12689 UI:setPage('methods', event.selected)\
12690\
12691 end\
12692 return UI.Page.eventHandler(self, event)\
12693 end,\
12694}\
12695\
12696--[[ -- MethodsPage -- ]] --\
12697local methodsPage = UI.Page {\
12698 doc = UI.TextArea {\
12699 backgroundColor = 'black',\
12700 ey = -7,\
12701 marginLeft = 1, marginTop = 1,\
12702 },\
12703 grid = UI.ScrollingGrid {\
12704 y = -6, ey = -2,\
12705 columns = {\
12706 { heading = 'Name', key = 'name' }\
12707 },\
12708 sortColumn = 'name',\
12709 },\
12710 statusBar = UI.StatusBar {\
12711 status = 'q to return',\
12712 },\
12713 accelerators = {\
12714 [ 'control-q' ] = 'back',\
12715 backspace = 'back',\
12716 },\
12717 enable = function(self, p)\
12718 self.peripheral = p or self.peripheral\
12719\
12720 p = device[self.peripheral.name]\
12721 if p.getDocs then\
12722 -- plethora\
12723 self.grid.values = { }\
12724 for k,v in pairs(p.getDocs()) do\
12725 table.insert(self.grid.values, {\
12726 name = k,\
12727 doc = v,\
12728 })\
12729 end\
12730 elseif not p.getAdvancedMethodsData then\
12731 -- computercraft\
12732 self.grid.values = { }\
12733 for k,v in pairs(p) do\
12734 if type(v) == 'function' then\
12735 table.insert(self.grid.values, {\
12736 name = k,\
12737 noext = true,\
12738 })\
12739 end\
12740 end\
12741 else\
12742 -- open peripherals\
12743 self.grid.values = p.getAdvancedMethodsData()\
12744 for name,f in pairs(self.grid.values) do\
12745 f.name = name\
12746 end\
12747 end\
12748\
12749 self.grid:update()\
12750 self.grid:setIndex(1)\
12751\
12752 self.doc:setText(self:getDocumentation())\
12753\
12754 self.statusBar:setStatus(self.peripheral.type)\
12755 UI.Page.enable(self)\
12756\
12757 self:setFocus(self.grid)\
12758 end,\
12759}\
12760\
12761function methodsPage:eventHandler(event)\
12762 if event.type == 'back' then\
12763 UI:setPage(peripheralsPage)\
12764 return true\
12765 elseif event.type == 'grid_focus_row' then\
12766 self.doc:setText(self:getDocumentation())\
12767 end\
12768 return UI.Page.eventHandler(self, event)\
12769end\
12770\
12771function methodsPage:getDocumentation()\
12772 local method = self.grid:getSelected()\
12773\
12774 if not method or method.noext then -- computercraft docs\
12775 return 'No documentation'\
12776 end\
12777\
12778 if method.doc then -- plethora docs\
12779 return Ansi.yellow .. method.doc\
12780 end\
12781\
12782 -- open peripherals docs\
12783 local sb = { }\
12784 if method.description then\
12785 table.insert(sb, method.description .. '\\n\\n')\
12786 end\
12787\
12788 if method.returnTypes ~= '()' then\
12789 table.insert(sb, Ansi.yellow .. method.returnTypes .. ' ')\
12790 end\
12791 table.insert(sb, Ansi.blue .. method.name .. Ansi.reset .. '(')\
12792\
12793 for k,arg in ipairs(method.args) do\
12794 if arg.optional then\
12795 table.insert(sb, Ansi.orange .. string.format('[%s]', arg.name))\
12796 else\
12797 table.insert(sb, Ansi.green .. arg.name)\
12798 end\
12799 if k < #method.args then\
12800 table.insert(sb, ',')\
12801 end\
12802 end\
12803 table.insert(sb, Ansi.reset .. ')')\
12804\
12805 Util.filterInplace(method.args, function(a) return #a.description > 0 end)\
12806 if #method.args > 0 then\
12807 table.insert(sb, '\\n\\n')\
12808 for k,arg in ipairs(method.args) do\
12809 if arg.optional then\
12810 table.insert(sb, Ansi.orange)\
12811 else\
12812 table.insert(sb, Ansi.green)\
12813 end\
12814 table.insert(sb, arg.name .. Ansi.reset .. ': ' .. arg.description)\
12815 if k ~= #method.args then\
12816 table.insert(sb, '\\n\\n')\
12817 end\
12818 end\
12819 end\
12820 return table.concat(sb)\
12821end\
12822\
12823Event.on('peripheral', function()\
12824 peripheralsPage:updatePeripherals()\
12825end)\
12826\
12827Event.on('peripheral_detach', function()\
12828 peripheralsPage:updatePeripherals()\
12829end)\
12830\
12831UI:setPage(peripheralsPage)\
12832\
12833UI:setPages({\
12834 methods = methodsPage,\
12835})\
12836\
12837UI:start()",
12838 },
12839}