Lupus590

[CC] Checkpoint

Mar 19th, 2018
450
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.43 KB | None | 0 0
  1. --[[
  2. -- @Name: Checkpoint
  3. -- @Author: Lupus590
  4. -- @License: MIT
  5. -- @URL: https://github.com/CC-Hive/Checkpoint
  6. --
  7. -- If you are interested in the above format: http://www.computercraft.info/forums2/index.php?/topic/18630-rfc-standard-for-program-metadata-for-graphical-shells-use/
  8. --
  9. -- Includes stack tracing code from SquidDev's Mildly Better Shell (Also known as MBS): http://www.computercraft.info/forums2/index.php?/topic/29253-mildly-better-shell-various-extensions-to-the-default-shell/
  10. --
  11. -- Checkpoint doesn't save your program's data, it must do that itself. Checkpoint only helps it to get to roughly the right area of code to resume execution.
  12. --
  13. -- One may want to have a table with needed data in which gets passed over checkpoints with each checkpoint segment first checking that this table exists and loading it from a file if it doesn't and the last thing it does before reaching the checkpoint is saving this table to that file.
  14. --
  15. -- Checkpoint's License:
  16. --
  17. --  The MIT License (MIT)
  18. --
  19. --  Copyright (c) 2018 Lupus590
  20. --
  21. --  Permission is hereby granted, free of charge, to any person obtaining a copy
  22. --  of this software and associated documentation files (the "Software"), to deal
  23. --  in the Software without restriction, including without limitation the rights
  24. --  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  25. --  copies of the Software, and to permit persons to whom the Software is
  26. --  furnished to do so, subject to the following conditions:
  27. --
  28. --  The above copyright notice and this permission notice shall be included in all
  29. --  copies or substantial portions of the Software.
  30. --
  31. --  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  32. --  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  33. --  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  34. --  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  35. --  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  36. --  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  37. --  SOFTWARE.
  38. --
  39. --
  40. -- MBS's License:
  41. --
  42. --  The MIT License (MIT)
  43. --
  44. --  Copyright (c) 2017 SquidDev
  45. --
  46. --  Permission is hereby granted, free of charge, to any person obtaining a copy
  47. --  of this software and associated documentation files (the "Software"), to deal
  48. --  in the Software without restriction, including without limitation the rights
  49. --  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  50. --  copies of the Software, and to permit persons to whom the Software is
  51. --  furnished to do so, subject to the following conditions:
  52. --
  53. --  The above copyright notice and this permission notice shall be included in all
  54. --  copies or substantial portions of the Software.
  55. --
  56. --  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  57. --  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  58. --  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  59. --  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  60. --  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  61. --  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  62. --  SOFTWARE.
  63. --
  64. --
  65. --]]
  66.  
  67. -- TODO: detect when we errored and warn somehow to prevent boot, error, reboot loops
  68. -- May be able to detect rebotes using some monstocity of os.clock os.time os.day
  69.  
  70.  
  71. -- TODO: cleanup code
  72.  
  73. local checkpoint = shell and {} or (_ENV or getfenv())
  74.  
  75. local checkpointFile = ".checkpoint"
  76.  
  77. local checkpoints = {}
  78.  
  79. local checkpointTrace = {}
  80.  
  81. local nextLabel
  82.  
  83. local useStackTracing = true -- sets default for the API, program can set at runtime with third arg to checkpoint.run
  84.  
  85. local intentionalError -- true if traceback function belives the error is intentional, false otherwise, nil if traceback has not be generated
  86.  
  87. -- MBS Stack Tracing
  88.  
  89. local function traceback(x)
  90.   -- Attempt to detect error() and error("xyz", 0).
  91.   -- This probably means they're erroring the program intentionally and so we
  92.   -- shouldn't display anything.
  93.   if x == nil or (type(x) == "string" and not x:find(":%d+:")) then
  94.     intentionalError = true
  95.     return x
  96.   end
  97.  
  98.   intentionalError = false
  99.   if type(debug) == "table" and type(debug.traceback) == "function" then
  100.     return debug.traceback(tostring(x), 2)
  101.   else
  102.     local level = 3
  103.     local out = { tostring(x), "stack traceback:" }
  104.     while true do
  105.       local _, msg = pcall(error, "", level)
  106.       if msg == "" then break end
  107.  
  108.       out[#out + 1] = "  " .. msg
  109.       level = level + 1
  110.     end
  111.  
  112.     return table.concat(out, "\n")
  113.   end
  114. end
  115.  
  116. local function trimTraceback(target, marker)
  117.   local ttarget, tmarker = {}, {}
  118.   for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
  119.   for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
  120.  
  121.   local t_len, m_len = #ttarget, #tmarker
  122.   while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
  123.     table.remove(ttarget, t_len)
  124.     t_len, m_len = t_len - 1, m_len - 1
  125.   end
  126.  
  127.   return ttarget
  128. end
  129.  
  130. -- ENd of MBS Stack Tracing
  131.  
  132.  
  133.  
  134.  
  135. function checkpoint.add(label, callback, ...)
  136.   if type(label) ~= "string" then error("Bad arg[1], expected string, got "..type(label), 2) end
  137.   if type(callback) ~= "function" then error("Bad arg[2], expected function, got "..type(callback), 2) end
  138.  
  139.   checkpoints[label] = {callback = callback, args = table.pack(...), }  
  140. end
  141.  
  142. function checkpoint.remove(label) -- this is intended for debugging, users can use it to make sure that their programs don't loop on itself when it's not meant to
  143.   if type(label) ~= "string" then error("Bad arg[1], expected string, got "..type(label), 2) end
  144.   if not checkpoints[label] then error("Bad arg[1], no known checkpoint with label "..tostring(label), 2) end
  145.  
  146.   checkpoints[label] = nil  
  147. end
  148.  
  149. function checkpoint.reach(label)
  150.   if type(label) ~= "string" then error("Bad arg[1], expected string, got "..type(label), 2) end
  151.   if not checkpoints[label] then error("Bad arg[1], no known checkpoint with label '"..tostring(label).."'. You may want to check spelling, scope and such.", 2) end
  152.  
  153.   local f = fs.open(checkpointFile,"w")
  154.   f.writeLine(label)
  155.   f.close()
  156.   nextLabel = label
  157. end
  158.  
  159. function checkpoint.run(defaultLabel, fileName, stackTracing) -- returns whatever the last callback returns (xpcall stuff stripped if used)
  160.   if type(defaultLabel) ~= "string" then error("Bad arg[1], expected string, got "..type(defaultLabel), 2) end
  161.   if not checkpoints[defaultLabel] then error("Bad arg[1], no known checkpoint with label "..tostring(defaultLabel), 2) end
  162.   if fileName and type(fileName) ~= "string" then error("Bad arg[2], expected string or nil, got "..type(fileName), 2) end
  163.   if stackTracing and type(stackTracing) ~= "boolean" then error("Bad arg[3], expected boolean or nil, got "..type(stackTracing), 2) end
  164.  
  165.   if stackTracing ~= nil then
  166.     useStackTracing = stackTracing
  167.   end
  168.  
  169.   checkpointFile = fileName or checkpointFile
  170.   nextLabel = defaultLabel
  171.  
  172.  
  173.  
  174.   if fs.exists(checkpointFile) then
  175.     local f = fs.open(checkpointFile, "r")
  176.     nextLabel = f.readLine()
  177.     f.close()
  178.     if not checkpoints[nextLabel] then error("Found checkpoint file '"..fileName.."' containing unknown label '"..nextLabel.."'. Are your sure that this is the right file and that nothing is changing it?", 0) end
  179.   end
  180.    
  181.  
  182.   local returnValues
  183.  
  184.   while nextLabel ~= nil do
  185.     local l = nextLabel
  186.     checkpointTrace[#checkpointTrace+1] = nextLabel
  187.     nextLabel = nil
  188.    
  189.     if useStackTracing then
  190.      
  191.      
  192.      
  193.       -- The following line is horrible, but we need to capture the current traceback and run
  194.       -- the function on the same line.
  195.       intentionalError = nil
  196.       returnValues = table.pack(xpcall(function() return checkpoints[l].callback(table.unpack(checkpoints[l].args, 1, checkpoints[l].args.n)) end, traceback))
  197.       local ok   = table.remove(returnValues, 1)
  198.       if not ok then
  199.         local trace = traceback("checkpoint.lua"..":1:")
  200.         local errorMessage = ""
  201.         if returnValues[1] ~= nil then
  202.           trace = trimTraceback(returnValues[1], trace)
  203.  
  204.           local max, remaining = 15, 10
  205.           if #trace > max then
  206.             for i = #trace - max, 0, -1 do table.remove(trace, remaining + i) end
  207.             table.insert(trace, remaining, "  ...")
  208.           end
  209.      
  210.          
  211.           errorMessage = table.concat(trace, "\n")
  212.          
  213.          
  214.           if intentionalError == false and errorMessage ~= "Terminated" then
  215.             errorMessage = errorMessage.."\n\nCheckpoints ran in this instance:\n  "..table.concat(checkpointTrace, "\n  ").." <- error occured in\n"
  216.           end
  217.          
  218.         end
  219.        
  220.         error(errorMessage, 0)
  221.       end -- if not ok
  222.     else
  223.       returnValues = table.pack(checkpoints[l].callback(table.unpack(checkpoints[l].args, 1, checkpoints[l].args.n)))
  224.     end
  225.    
  226.   end
  227.    
  228.  
  229.   -- we have finished the program, delete the checkpointFile so that the program starts from the beginning if ran again
  230.   if fs.exists(checkpointFile) then
  231.     fs.delete(checkpointFile)
  232.   end
  233.    
  234.   return type(returnValues) == "table" and table.unpack(returnValues, 1, returnValues.n) or returnValues -- if it's a table, return the unpacked table, else return whatever it is
  235.  
  236.  end
  237.  
  238.  
  239. return checkpoint
Advertisement
Add Comment
Please, Sign In to add comment