Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local lightweightBS = true --BS == back stepping
- local heavyweightBS = true
- local dname = shell.getRunningProgram()
- local function loopComplete(a,b,sLine,sParts)
- table.remove(sParts,1)
- return shell.complete(table.concat(sParts," ").." "..sLine)
- end
- shell.setCompletionFunction(dname,loopComplete)
- local breakpoints = {}
- local env = {}
- local locals = {}
- local paused = true
- local curScope = {vars={},pScope={vars={}}} --pScope == globals; set in errorLoopBack
- local dlevel
- local history = {}
- local historyIndex = 1 --set in errorLoopBack
- local targetHistoryIndex
- local makeHistory = true
- local luaHistory = {}
- local oldENV = _ENV
- local loadedDocs = {}
- local nilTable = {} --used instead of nil; since nil removes table entries
- local unsetTable = {}
- local debnumber = math.random(10000)
- local lastIndexedH
- local lastFunctionCall
- local meta = {}
- local submeta = {}
- local rtf = {} --real to fake table map
- local ftr = {}
- local wtu = {} -- wrapped to unwrapped function map
- local utw = {}
- local args = {...}
- args[1] = shell.resolveProgram(args[1] or "")
- if #args == 0 then
- print("Usage: ".. shell.getRunningProgram() .." <prog to debug> [params]")
- return
- end
- if not fs.exists(args[1]) then
- print "File not found"
- return
- end
- if not multishell then
- print [[F4 - Print variables set by prog
- F5 - step backwards
- F6 - step into (if theres own function)
- F7 - step over
- F8 - run until breakpoint
- F9 - toggle breakpoint (enter line num before)]]
- end
- local function varsToString(t)
- local r = ""
- for i,j in oldENV.pairs(t or {}) do
- r = r .. oldENV.tostring(i) .. ": "
- if j == nilTable then
- r = r .. "nil\n"
- elseif oldENV.type(j) == "table" then
- r = r .. oldENV.textutils.serialize(j) .. "\n"
- else
- r = r .. oldENV.tostring(j) .. "\n"
- end
- end
- return r
- end
- local function n() return true end
- local draw = n
- local undoers = {
- [sleep]=n,
- [print]=n,
- [write]=n,
- [read]=n,
- [getfenv]=n,
- [getmetatable]=n,
- [pairs]=n,
- [ipairs]=n,
- [next]=n,
- [tostring]=n,
- [tonumber]=n}
- do
- local undots = {math,term,textutils,bit,string,table,vector}
- for i,j in ipairs(undots) do
- for k,l in pairs(j) do
- if type(l) == "function" then
- undoers[l] = n
- end
- end
- end
- end
- if turtle then
- undoers[turtle.forward] = turtle.back
- undoers[turtle.back] = turtle.forward
- undoers[turtle.turnLeft] = turtle.turnRight
- undoers[turtle.turnRight] = turtle.turnLeft
- undoers[turtle.up] = turtle.down
- undoers[turtle.down] = turtle.up
- undoers[turtle.getFuelLevel] = n
- undoers[turtle.getItemCount] = n
- undoers[turtle.getItemSpace] = n
- undoers[turtle.inspect] = n
- undoers[turtle.inspectUp] = n
- undoers[turtle.inspectDown] = n
- undoers[turtle.compare] = n
- undoers[turtle.compareUp] = n
- undoers[turtle.compareDown] = n
- undoers[turtle.detect] = n
- undoers[turtle.detectUp] = n
- undoers[turtle.detectDown] = n
- undoers[turtle.dig] = turtle.place
- undoers[turtle.place] = turtle.dig
- undoers[turtle.digUp] = turtle.placeUp
- undoers[turtle.placeUp] = turtle.digUp
- undoers[turtle.digDown] = turtle.placeDown
- undoers[turtle.placeDown] = turtle.digDown
- end
- function env._lbpcf() -- line breakpoint call function
- local a,sLine = oldENV.pcall(oldENV.error,"",3)
- if sLine:match("debugger:") then
- a,sLine = oldENV.pcall(oldENV.error,"",4)
- end
- local docname,line = sLine:match("^(.-):(%d+):")
- line = oldENV.tonumber(line)
- local linesToRepeat = {}
- local missedEvents = {}
- repeat
- if (not targetHistoryIndex or historyIndex >= targetHistoryIndex) and (paused and (not dlevel or dlevel == curScope) or breakpoints[line]) then
- local bline = ""
- targetHistoryIndex = nil
- paused = true
- while paused do
- draw(docname,line)
- local r = {oldENV.os.pullEvent()}
- if r[1] == "key" or (r[1] == "debugger_key" and r[3] == debnumber) then
- if r[2] == 62 then
- local localsEnd = false
- local output = "CurrentLine: "..line.."\nLocals:\n"
- local inScope = curScope
- while inScope.pScope do
- output = output .. varsToString(inScope.vars)
- if not localsEnd and inScope.func then
- output = output .. "Upvalues:\n"
- localsEnd = true
- end
- inScope = inScope.pScope
- end
- output = output .. "Globals:\n" .. varsToString(inScope.vars)
- if history[historyIndex] and history[historyIndex].calls then
- output = output .. "Function Calls:"
- for i,j in oldENV.ipairs(history[historyIndex].calls) do
- output = output .."\n"..j[1]
- for k,l in oldENV.ipairs(j[3]) do
- output = output .." "..l
- end
- end
- output = output .. "\n"
- end
- oldENV.textutils.pagedPrint(output)
- elseif r[2] == 63 and historyIndex > 1 then
- local lastLine = line -1
- local lastBlock = loadedDocs[docname].injec[lastLine]
- while loadedDocs[docname].blockedLines[lastLine] do
- lastLine=lastLine-1
- lastBlock = loadedDocs[docname].injec[lastLine] .. lastBlock
- end
- local loadedBlock = oldENV.loadstring(lastBlock,"RepeatBlock")
- local allReversable = true
- if history[historyIndex] and history[historyIndex].calls then
- for i=#history[historyIndex].calls,1,-1 do
- if not undoers[history[historyIndex].calls[i][2]] then
- allReversable = false
- break
- end
- end
- if allReversable then
- for i=#history[historyIndex].calls,1,-1 do
- local cl = history[historyIndex].calls[i]
- if cl[4][1] ~= false and not undoers[cl[2]](oldENV.unpack(cl,3)) then
- for j=i,#history[historyIndex].calls do
- local clj = history[historyIndex].calls[i]
- oldENV.pcall(clj[2],oldENV.unpack(clj,3))
- end
- allReversable = false
- break
- end
- end
- end
- end
- if allReversable then
- if lightweightBS and loadedBlock and not (history[historyIndex] and history[historyIndex].ownFuncCalled) then
- if history[historyIndex] and history[historyIndex].modifs then
- for t,j in oldENV.pairs(history[historyIndex].modifs) do
- for k,v in oldENV.pairs(j) do
- if v == unsetTable then
- t[k] = nil
- else
- t[k] = v
- end
- end
- end
- end
- oldENV.setfenv(loadedBlock,env)
- history[historyIndex] = {}
- historyIndex = historyIndex - 1
- lastIndexedH = nil
- linesToRepeat[#linesToRepeat+1] = loadedBlock
- line = lastLine
- elseif heavyweightBS then
- oldENV.error("backstab",0)
- end
- end
- elseif r[2] == 64 then
- dlevel = nil
- break
- elseif r[2] == 65 then
- dlevel = curScope
- break
- elseif r[2] == 66 then
- paused = false
- break
- elseif r[2] == 67 then
- local bp = oldENV.tonumber(bline)
- if bp then
- breakpoints[bp] = not breakpoints[bp]
- end
- elseif r[2] == 68 then
- oldENV.write"lua> "
- local s = oldENV.read(nil,luaHistory)
- oldENV.table.insert(luaHistory,s)
- local ok = oldENV.loadstring("return "..s)
- if not ok then
- ok = oldENV.loadstring(s)
- end
- if ok then
- oldENV.setfenv(ok,env)
- makeHistory = false
- local r={oldENV.pcall(ok)}
- makeHistory = true
- oldENV.table.remove(r,1)
- for i,j in oldENV.ipairs(r) do
- r[i] = oldENV.type(j) == "table" and oldENV.textutils.serialize(ftr[j]) or oldENV.tostring(j)
- end
- oldENV.print(oldENV.table.concat(r,", "))
- else
- oldENV.printError"Syntax error"
- end
- elseif r[2] >= 2 and r[2] <= 11 then
- bline = bline .. oldENV.tostring(r[2]-1):sub(-1)
- end
- elseif r[1] ~= "char" then
- oldENV.table.insert(missedEvents,r)
- end
- end
- end
- for i,j in oldENV.ipairs(missedEvents) do
- oldENV.os.queueEvent(oldENV.unpack(j))
- end
- missedEvents = {}
- a = true
- historyIndex = historyIndex + 1
- if #linesToRepeat > 0 then
- oldENV.pcall(linesToRepeat[#linesToRepeat])
- linesToRepeat[#linesToRepeat] = nil
- line = line + 1
- a=false
- end
- until #linesToRepeat == 0 and a
- end
- function env._bscf(loop) --block start call function
- curScope = {vars={},pScope=curScope,isLoop=loop}
- end
- function env._becf(loop) --block end call function
- while loop and not curScope.isLoop do
- curScope = curScope.pScope
- end
- if not curScope.func then
- curScope = curScope.pScope
- end
- end
- local function logChange(rt,k)
- history[historyIndex] = history[historyIndex] or {}
- history[historyIndex].modifs = history[historyIndex].modifs or {}
- history[historyIndex].modifs[rt] = history[historyIndex].modifs[rt] or {}
- if history[historyIndex].modifs[rt][k] == nil then
- history[historyIndex].modifs[rt][k] = rt[k] or unsetTable
- end
- end
- function env._cnlv(decla) --create new local variable
- for var in oldENV.string.gmatch(decla,"%w+") do
- logChange(curScope.vars,var)
- curScope.vars[var] = nilTable
- end
- end
- local function protTable(t,k,isR)
- local rt = isR and t or ftr[t]
- local v = rt[k]
- local tp = oldENV.type(v)
- if tp == "table" then
- if v == nilTable then return nil end
- if not rtf[v] then
- local fake = oldENV.setmetatable({},submeta)
- rtf[v] = fake
- ftr[fake] = v
- end
- return rtf[v]
- elseif tp == "function" then
- if not utw[v] then
- local function wrappy(...) --wrapping for extern
- if targetHistoryIndex then
- if lastIndexedH ~= historyIndex then
- lastFunctionCall = 0
- lastIndexedH = historyIndex
- end
- lastFunctionCall = lastFunctionCall + 1
- oldENV.assert(history[historyIndex] and history[historyIndex].calls and history[historyIndex].calls[lastFunctionCall],"Tried to call "..k.." at step "..historyIndex.. " call no "..lastFunctionCall)
- oldENV.assert(k == history[historyIndex].calls[lastFunctionCall][1],"Wrong function was called")
- return oldENV.unpack(history[historyIndex].calls[lastFunctionCall][4])
- elseif makeHistory then
- history[historyIndex] = history[historyIndex] or {}
- history[historyIndex].calls = history[historyIndex].calls or {}
- local callTable = {k,v,{...}}
- oldENV.table.insert(history[historyIndex].calls,callTable)
- local r = {v(...)}
- callTable[4] = r
- return oldENV.unpack(r)
- else
- return v(...)
- end
- end
- utw[v] = wrappy
- wtu[wrappy] = v
- end
- return utw[v]
- end
- return v
- end
- submeta.__index = protTable
- local function recuMakeReal(t)
- for i,j in oldENV.pairs(t) do
- if oldENV.type(j) == "table" then
- if ftr[j] then
- t[i] = ftr[j]
- else
- recuMakeReal(j)
- end
- end
- end
- end
- local function newSubIndex(t,k,v,isR)
- if oldENV.type(v) == "function" and not wtu[v] then
- local fScope = curScope
- local function wrappy(...) --wrapping for intern
- local prevScope = curScope
- curScope = {vars={},pScope=fScope}
- history[historyIndex] = history[historyIndex] or {}
- history[historyIndex].ownFuncCalled = true
- --[[history[historyIndex].calls = history[historyIndex].calls or {}
- local callTable = {k,v,{...}}
- oldENV.table.insert(history[historyIndex].calls,callTable)]]
- local r = {v(...)}
- --callTable[4] = r
- curScope = prevScope
- return oldENV.unpack(r)
- end
- oldENV.setfenv(wrappy,env)
- wtu[wrappy] = v
- utw[v] = wrappy
- elseif oldENV.type(v) == "table" then
- recuMakeReal(v)
- end
- local rt= isR and t or ftr[t]
- logChange(rt,k)
- rt[k] = (v == nil and nilTable or ftr[v] or v)
- end
- submeta.__newindex = newSubIndex
- local metamethods = {"add","sub","mul","div","mod","pow","concat","eq","lt","le"}
- for i,j in ipairs(metamethods) do
- local name = "__"..j
- submeta[name] = function(a,b)
- local ra = ftr[a]
- local rb = ftr[b] or b
- local mt = getmetatable(ra)
- if mt then
- return mt[name](ra,rb)
- end
- end
- end
- function submeta.__call(a,...)
- return ftr[a](...)
- end
- function submeta.__unm(a) --__len replacement
- return #ftr[a]
- end
- function submeta.__len(a)
- return #ftr[a]
- end
- function meta.__index(t,k)
- local inScope = curScope
- while inScope.vars do
- if inScope.vars[k] then
- if inScope.vars[k] == nilTable then
- return nil
- end
- return protTable(inScope.vars,k,true)
- end
- inScope = inScope.pScope or {}
- end
- return protTable(oldENV,k,true)
- end
- function meta.__newindex(t,k,v)
- local inScope = curScope
- while inScope.pScope and inScope.vars[k] == nil do
- inScope = inScope.pScope
- end
- newSubIndex(inScope.vars,k,v,true)
- end
- function env.next(t,v)
- if ftr[t] then
- local k,o = oldENV.next(ftr[t],v)
- if k == nil then return nil end
- return k,protTable(t,k)
- else
- return oldENV.next(t,v)
- end
- end
- function env.pairs(t)
- return env.next,t
- end
- function env.__inext(t,v)
- if ftr[t] then
- local k,o = oldENV.__inext(ftr[t],v)
- if k == nil then return nil end
- return k,protTable(t,k)
- else
- return oldENV.__inext(t,v)
- end
- end
- function env.ipairs(t)
- return env.__inext,t,0
- end
- local spat = "(%s)"
- local epat = ""
- local patterns = {"then",
- "do",
- "repeat",
- "end",
- "until",
- "else",
- "elseif",
- "break(%s-) _becf%(%)",
- "return(.-) _becf%(%)",
- spat.."function(%s*)(%w+)(%s*)%(",
- spat.."local([ \t,%w]-)=",
- "local([ \t,%w]+)",
- "#"}
- local replaces = {"%1then _bscf();%2",
- "%1do _bscf(true);%2",
- "%1repeat _bscf(true)%2",
- "%1 _becf() end%2",
- "%1 _becf() until%2",
- "%1 _becf() else _bscf();%2",
- "%1 _becf() elseif%2",
- "%1break%2%3",
- "%1return%2%3",
- "%1%2%3 = function%4(",
- "%1_cnlv(\"%2\");%2=",
- "%1_cnlv(\"%2\");%3",
- "-"}
- local notSPats = {[10]=true,[11]=true,[13]=true}
- function env.loadstring(s,name)
- local ok,err = oldENV.loadstring(s,name)
- if not ok then
- return ok,err
- end
- local doc = s:sub(-1)~="\n" and s.."\n" or s
- loadedDocs[name] = {orig={},injec={}}
- local hasBreakReturn
- for line in doc:gmatch("(.-)\n") do
- oldENV.table.insert(loadedDocs[name].orig,line)
- end
- doc = " "..doc
- for i,j in oldENV.ipairs(patterns) do
- if notSPats[i] then
- doc = doc:gsub(j,replaces[i])
- else
- doc = doc:gsub(spat..j..spat,replaces[i])
- end
- end
- for line in doc:gmatch("(.-)\n") do
- oldENV.table.insert(loadedDocs[name].injec,line)
- end
- assert(#loadedDocs[name].orig == #loadedDocs[name].injec, "Modification line numbers invalid")
- local blockedLines = {}
- repeat
- local prog = ""
- for i,j in oldENV.ipairs(loadedDocs[name].injec) do
- if not blockedLines[i] then
- prog = prog .."_lbpcf();"
- end
- prog = prog ..j .. "\n"
- end
- ok,err = oldENV.loadstring(prog,name)
- local fh = fs.open(".crash","w")
- fh.write(prog)
- fh.close()
- if not ok then
- local ln = err:match(".-:.-:.-:(%d+):")
- if ln and oldENV.tonumber(ln) and not blockedLines[oldENV.tonumber(ln)] then
- blockedLines[oldENV.tonumber(ln)] = true
- else
- return false,"Debugger failed: "..err
- end
- end
- until ok
- loadedDocs[name].blockedLines = blockedLines
- return ok,err
- end
- setmetatable(env,meta)
- local fh = fs.open(args[1],"r")
- local fcont = fh.readAll()
- fh.close()
- local tok,terr = env.loadstring(fcont,args[1])
- if tok then
- local function errorLoopBack(...)
- local fin,err
- repeat
- historyIndex = 1
- curScope = {vars={},pScope={vars={}}}
- fin,err = oldENV.pcall(tok,...)
- if not fin then
- history[historyIndex] = nil
- targetHistoryIndex = historyIndex - 1
- paused = true
- lastIndexedH = nil
- if err ~= "backstab" then
- oldENV.printError(err)
- end
- end
- until fin or err == "Terminated"
- return fin,err
- end
- setfenv(tok,env)
- if multishell then
- _G.mainF ={errorLoopBack,unpack(args,2)}
- local fh = fs.open(".temp","w")
- fh.write([[local a,b = pcall(unpack(_G.mainF))
- if not a then printError(b) end
- os.queueEvent("debugger_end",]]..debnumber..")")
- fh.close()
- local x,y = term.getSize()
- local buttons = {"<-","->","O>","B>","SB"}
- local t = term.current()
- local topLine = 1
- draw = function(name,line)
- topLine = line-(line-1)%(y-1)
- for i=0,y-1 do
- t.setCursorPos(1,i+1)
- t.blit(oldENV.string.format("%3d",(topLine+i)%1000),line - topLine == i and "aaa" or "fff", breakpoints[topLine+i] and "eee" or "444")
- assert(loadedDocs[name],"No Doc: "..name)
- t.write(((loadedDocs[name].orig[topLine+i] or "").. oldENV.string.rep(" ",x-17)):sub(1,x-17))
- end
- for i=1,#buttons do
- t.setCursorPos(x-13,i*2-1)
- t.blit(buttons[i],"11","44")
- end
- local i = 1
- local inScope = curScope
- local key,value
- while i < y do
- t.setCursorPos(x-11,i)
- if inScope then
- repeat
- key,value = oldENV.next(inScope.vars,key)
- if key == nil then
- inScope = inScope.pScope
- end
- until key ~= nil or not inScope
- end
- i=i+1
- if key ~= nil and inScope then
- t.write((oldENV.tostring(key).."="..oldENV.tostring(value).." "):sub(1,12))
- else
- t.write" "
- end
- end
- end
- term.clear()
- shell.openTab(".temp")
- multishell.setTitle(multishell.getCount(),string.match(args[1],"(%a+)$") or "Unnamed")
- local event
- repeat
- event = {os.pullEvent()}
- if event[1] == "key" and event[2] >= 62 and event[2] <= 68 then
- os.queueEvent("debugger_key",event[2],debnumber)
- end
- if event[1] == "key" and event[2] == 64 then
- paused = true
- dlevel = nil
- end
- if event[1] == "mouse_click" and event[2] == 1 then
- if (event[3] == x-13 or event[3] == x-12) and event[4] % 2 == 1 then
- os.queueEvent("debugger_key",62.5+event[4]/2,debnumber)
- end
- if event[3] <= 3 then
- local clicked = topLine+event[4]-1
- breakpoints[clicked] = not breakpoints[clicked]
- os.queueEvent("update_draw")
- end
- end
- until event[1] == "debugger_end" and event[2] == debnumber
- term.clear()
- term.setCursorPos(1,1)
- else
- local ok,rerr = pcall(errorLoopBack,unpack(args,2))
- if not ok then
- print("Crashed:")
- print(rerr)
- end
- end
- else
- print(terr)
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement