Help with inspection of `debug.traceback()` strings?

I still find the syntax for complex string inspection befuddling, so I’m hoping I can get some help with this issue.

Basically I need to compare the locations that two different commands are coming from. I get this info using debug.traceback().

For example, here’s a typical output from print(debug.traceback()):


stack traceback:
	SimpleButtons:178: in function 'button'
	greetingScreen:31: in function 'greetingScreen'
	Main:16: in global '_wrap_draw'
	SupportedOrientation :260: in function <SupportedOrientation :242>

As you can see, each level of the stack reports the tab it was called from, then the line number it was called from, and finally the function it was called from.

So here’s the situation I’m confronting: I have a current traceback that I need to compare to a table of stored tracebacks. I need to find the value in the table that most closely matches the current traceback.

My idea is to break each traceback string into a table of subtables, with each subtable breaking down the data in one level of the stack. I guess that means that what I need to know how to do is:

  • examine each line after “stack traceback” one by one
  • get the substring before the first colon
  • get the substring between the first and second colons
  • get the substring that’s between two delimiting characters, which can be either single quotes or brackets, at the end of the line

So, for instance, with that knowledge I could turn the above traceback into this:


currentTraceback = {
        {“SimpleButtons”, ”178”, “button”},
	    {“greetingScreen”, “31”, “greetingScreen”},
	    {“Main”, “16”, “_wrap_draw”},
	    {“SupportedOrientation”, “260”, “<SupportedOrientation :242>“}
}

…then, I think, it would be easy to compare each line of the current traceback to the equivalent line in a stored table of tracebacks.

…is there a substring wizard out there who can help me figure this out?

If you want to search thru the table for something specific, sort the table and then scroll thru it to find what you want.

@dave1707 I’m not sure what you mean.

I have big complicated strings (the tracebacks) that I need to compare to other big complicated strings, and I think the best way to do that is to make tables out of the strings.

When you write “sort the table and scroll through it to find what you want” it sounds like you think I already have those tables, which I don’t, and in fact what I’m asking is how to make those tables in the first place.

Are you trying to compare multiple trace back files to each other to see how the execution differs. I guess I don’t understand exactly what you’re trying to accomplish. I guess I’ll have to try the trace back and see what I get.

@dave1707 I need to be able to compare tracebacks to find the ones that are most identical.

To do this I think I have to be able to compare tracebacks using several criteria:

  • How many layers in the stack?
  • What functions are in each layer?
  • What tab is each function called from?
  • What line inside each function is the call coming from?

To make those comparisons, I think I have to be able to separate a traceback string into a set of tables that contain each of those criteria as separate elements.

@UberGoober Would something like this work. I just did some calls with trace back to try something. Let me know if you can work with this or if you need something changed.

Do table.insert(tab,debug.traceback()) to put the trace back into a table.
Then call fixTraceback() to format the info. This sorts the info. If you don’t want it sorted, remove the line table.sort(tab1,b) .

Slide your finger to scroll up/down.

fixTraceback does all the formatting and puts it into another table.

viewer.mode=FULLSCREEN

function setup()
    dy=0
    textMode(CORNER)
    fill(255)
    tab={}
    tab1={}
    for z=1,10 do
        aa() 
        bb()
    end
    fixTraceback()
end

function draw()
    background()
    for a,b in pairs(tab1) do
        text(b,10,HEIGHT-a*20+dy)
    end
end

function touched(t)
    if t.state==CHANGED then
        dy=dy+t.deltaY
    end
end

function fixTraceback()
    for a,b in pairs(tab) do
        b=string.gsub(b,"stack traceback:","")
        b=string.gsub(b,"\\10"," ")
        b=string.gsub(b,"\\09"," ")
        table.insert(tab1,b)
    end
    table.sort(tab1)
end

function aa()
    table.insert(tab,debug.traceback())
    bb()
    dd()
end

function bb()
    table.insert(tab,debug.traceback())
    cc()
end

function cc()
    table.insert(tab,debug.traceback())
end

function dd()
    table.insert(tab,debug.traceback())
    for v=1,2 do
        table.insert(tab,debug.traceback())
        print("p")
    end
end

@dave1707 thank you for doing that. I’m not sure I fully understand how the string is being broken down before it’s reassembled.

This is the kind of thing I need to be able to do, in the end:


viewer.mode=FULLSCREEN

function setup()
    t = getTraceback()
    tbl = tracebackTable(t)
end

function draw()
    background()
end

function getTraceback()
    return debug.traceback()
end

function tracebackTable(t)
    
    --some code here that ends up making this table:
    
    return {
        {"Main2", "16", "getTraceback"},
        {"Main2", "5", "setup"},
    }
end

…would I be able to do that with the way you’re separating the strings?

It took a lot of fixing on the trace back, but this looks right.

viewer.mode=STANDARD

function setup()
    dy=0
    textMode(CORNER)
    fill(255)
    tab={}
    tab1={}
    for z=1,10 do
        aa()
        bb()
        dd()
    end
    fixTraceback()
end

function draw()
    background()
    for a,b in pairs(tab1) do
        text(b,10,HEIGHT-a*20+dy)
    end
end

function touched(t)
    if t.state==CHANGED then
        dy=dy+t.deltaY
    end
end

function fixTraceback()
    for a,b in pairs(tab) do
        b=string.gsub(b,"stack traceback:","")
        b=string.gsub(b,"in function","")
        b=string.gsub(b,"  ","")
        b=string.gsub(b,"\\10\\09","{")
        b=string.gsub(b,"'{","'},{")
        b=string.gsub(b,":",",")
        b=string.gsub(b,"},",":")
        b=b.."}"
        b=string.gsub(b,",","','")
        b=string.gsub(b,"''","'")
        b=string.gsub(b,":","},")
        b=string.gsub(b,"{","{'")
        b=string.gsub(b,"'",'"')
        table.insert(tab1,b)
    end
    --table.sort(tab1)
end

function aa()
    table.insert(tab,debug.traceback())
end

function bb()
    table.insert(tab,debug.traceback())
    cc()
end

function cc()
    table.insert(tab,debug.traceback())
end

function dd()
    table.insert(tab,debug.traceback())
    for v=1,2 do
        table.insert(tab,debug.traceback())
        print("p")
    end
end

@dave1707 this looks great and I can’t wait to try it out.

@UberGoober Heres another version using gmatch.

viewer.mode=STANDARD

function setup()
    tab={}
    tab1={}
    for z=1,1 do
        dd()
    end
    for a,b in pairs(tab) do
        print(b)
    end

    pos=0
    for w,r in pairs(tab) do
        pos=pos+1
        sub=1
        tab1[pos]={}
        for a,b,c in string.gmatch(r,"(%a*):(%d*): in function '(%a*)'") do
            tab1[pos][sub]=a..","..b..","..c
            print("["..pos.."]["..sub.."]  ",tab1[pos][sub])
            sub=sub+1
        end    
    end
end

function dd()
    table.insert(tab,debug.traceback())
    ee()   
    table.insert(tab,debug.traceback())
    ff()
end

function ee()
    table.insert(tab,debug.traceback())
    ff()    
end

function ff()
    table.insert(tab,debug.traceback())
end

@dave1707 I tried it and I realized I’ve pointed in the wrong direction, because I was unclear in an important way: I actually am trying to get tables made, not strings that look like tables when they’re printed out.

I’ve tried altering the first code you set up, to demonstrate what I’m after, and I think it might be working, but I also think I’ve turned your nice compact code into a sprawling mess because ultimately it was just trial and error for me:


viewer.mode=STANDARD

function setup()
    dy=0
    textMode(CORNER)
    fill(255)
    tab={}
    tab1={}
   -- for z=1,10 do
    for z=1,1 do
        aa()
        bb()
        dd()
    end
    fixTraceback()
    local splitPerLine, linesSplitByWord = {}, {}
    splitPerLine = splitIntoOneTablePerStackLine(tab1[1])
    for i = 1, #splitPerLine do
        table.insert(linesSplitByWord, splitIntoSeparateItems(splitPerLine[i]))
    end
    print(table.unpack(linesSplitByWord))
    print(linesSplitByWord[1], ": ", table.unpack(linesSplitByWord[1]))
    print(linesSplitByWord[2], ": ", table.unpack(linesSplitByWord[2]))
end

function draw()
    background()
    for a,b in pairs(tab1) do
        --text(b,10,HEIGHT-a*20+dy)
    end
end

function touched(t)
    if t.state==CHANGED then
        dy=dy+t.deltaY
    end
end

function splitIntoOneTablePerStackLine(fixedStackString)
    local stackLines = {}
    local subString, indexToSplitAt
    while true do
        indexToSplitAt = string.find(fixedStackString, "}")
        if indexToSplitAt == nil then
            goto outOfWhile
        else
            local upToClosingBracket = string.sub(fixedStackString, 2, indexToSplitAt - 1)
            table.insert(stackLines, upToClosingBracket)
            fixedStackString = string.sub(fixedStackString, indexToSplitAt + 1)
        end
    end
    ::outOfWhile::
    return stackLines
end

function splitIntoSeparateItems(stackLine)
    local returnTable = {}
    local stackLineString
    local firstComma = string.find(stackLine, ",")
    table.insert(returnTable, string.sub(stackLine, 1, firstComma - 1))
    stackLineString = string.sub(stackLine, firstComma + 1)
    local secondComma = string.find(stackLineString, ",")
    table.insert(returnTable, string.sub(stackLineString, 1, secondComma - 1))
    stackLineString = string.sub(stackLineString, secondComma + 1)
    table.insert(returnTable, string.sub(stackLineString, 1))
    return returnTable
end

function fixTraceback()
    for a,b in pairs(tab) do
        b=string.gsub(b,"stack traceback:","")
        b=string.gsub(b,"in function","")
        b=string.gsub(b,"  ","")
        b=string.gsub(b,"\\10\\09","{")
        b=string.gsub(b,"'{","},{")
        b=string.gsub(b,":",",")
        b=string.gsub(b,"},",":")
        b=b.."}"
        b=string.gsub(b,",",",")
        b=string.gsub(b,"''","")
        b=string.gsub(b,":","}")
        b=string.gsub(b,"{","{")
        b=string.gsub(b,"'",'')
        table.insert(tab1,b)
    end
end

function aa()
    table.insert(tab,debug.traceback())
end

function bb()
    table.insert(tab,debug.traceback())
    cc()
end

function cc()
    table.insert(tab,debug.traceback())
end

function dd()
    table.insert(tab,debug.traceback())
    for v=1,2 do
        table.insert(tab,debug.traceback())
       -- print("p")
    end
end

I’m pretty positive there’s a better way to do it though.

@UberGoober I haven’t looked at your code yet, but the last example I posted creates each trace back in a multi dimensional table. If a trace back results in 3 calls, then there will be 3 occurances tab [1] [3] . If the next trace back has 5 occurances then it will be tab [2] [5] and so on. Each occurrence will have the string function,line number,function .

When I run it it seems to leave off the tab name.

This is what I get when I run it. Here’s the first 2 trace backs and what I put in the table for them. What are you running this on. Maybe different devices have different trace back formats. I’m on an iPad Air 3.

stack traceback:
Main:27: in function ‘dd’
Main:8: in function ‘setup’

stack traceback:
Main:34: in function ‘ee’
Main:28: in function ‘dd’
Main:8: in function ‘setup’

[1][1] Main,27,dd
[1][2] Main,8,setup
[2][1] Main,34,ee
[2][2] Main,28,dd
[2][3] Main,8,setup

Run this code and compare it to what I have. Just wondering if there are some invisible characters you have that I don’t. The line numbers might be different depending on how many blank lines you have before the code.

function setup()
    tab={}
    tab1={}
    dd()
    print(tab[1])
    for z=1,#tab[1] do
        v=string.sub(tab[1],z,z)
        table.insert(tab1,string.byte(v))
    end
    print(table.concat(tab1," "))
end

function dd()
    table.insert(tab,debug.traceback())
end
stack traceback:
	Main:17: in function 'dd'
	Main:7: in function 'setup'


115 116 97 99 107 32 116 114 97 99 101 98 97 99 107 58 10 9 77 97 105 110 58 49 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 100 100 39 10 9 77 97 105 110 58 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 115 101 116 117 112 39

My results:


stack traceback:
	M4:17: in function 'dd'
	M4:7: in function 'setup'

115 116 97 99 107 32 116 114 97 99 101 98 97 99 107 58 10 9 77 52 58 49 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 100 100 39 10 9 77 52 58 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 115 101 116 117 112 39

(iPad Pro)

Weirder and weirder.

I tried to explicate the variables in your gmatch code so that I could understand it easier. This is what it looks like after that:


viewer.mode=STANDARD

function setup()
    tracebacksRaw={}
    tracebacksProcessed={}
    for z=1,1 do
        startCallChain()
    end
    for _, traceback in pairs(tracebacksRaw) do
        print(traceback)
    end
    
    traceNumber=0
    for _, traceback in pairs(tracebacksRaw) do
        traceNumber=traceNumber+1
        stackLine=1
        tracebacksProcessed[traceNumber]={}
        for tabName,lineNumber,functionName in string.gmatch(traceback,"(%a*):(%d*): in function '(%a*)'") do
            tracebacksProcessed[traceNumber][stackLine]=tabName..","..lineNumber..","..functionName
            print("["..traceNumber.."]["..stackLine.."]  ",tracebacksProcessed[traceNumber][stackLine])
            stackLine=stackLine+1
        end    
    end
end

function startCallChain()
    table.insert(tracebacksRaw,debug.traceback())
    function2InChain()   
    table.insert(tracebacksRaw,debug.traceback())
    function3InChain()
end

function function2InChain()
    table.insert(tracebacksRaw,debug.traceback())
    function3InChain()    
end

function function3InChain()
    table.insert(tracebacksRaw,debug.traceback())
end

…but I did something wrong, because look at the output (attached).

You can see that the tracebacks are being captured accurately, but when they’re chopped up only “setup” and “startCallChain” are mentioned —there’s no sign of the other two calls in the chain.

Can you see where I went wrong? I’ve double-checked it and it looks the same to me.

@UberGoober I tried it on my iPad Pro and I got the same results as on my iPad Air 3. I also tried it on my iPhone 8 SE and got the same results as the Air 3. Not sure why you’re getting M4 and not Main. I can probably change the parameters in the gmatch to account for a number in the first position (M4). Let me try some function names that have numbers in them and see what happens.

PS. I tried some numbers in the function names and things didn’t turn out too well. Didn’t get anything like what I should have. So let me change the gmatch parameters and see what happens I guess I should also account for underscores in a function name.

The gmatch parameters are pretty specific as to what it looks for and if it doesn’t match exactly, it ignores stuff.

Try changing the gmatch to this.

        for a,b,c in string.gmatch(r,"(%g*):(%g*): in function '(%g*)'") do

I ran your latest code and I changed the gmatch parameters to %g* and this is what I got for the first 2 tracebacks.

stack traceback:
	Main:27: in function 'startCallChain'
	Main:7: in function 'setup'


stack traceback:
	Main:34: in function 'function2InChain'
	Main:28: in function 'startCallChain'
	Main:7: in function 'setup'


[1][1]  	Main,27,startCallChain
[1][2]  	Main,7,setup

[2][1]  	Main,34,function2InChain
[2][2]  	Main,28,startCallChain
[2][3]  	Main,7,setup

“M2” refers to a tab I added after Main, it stands for “Main2”.

When you post a bunch of different code on a single theme, I tend to make it one project and just add each piece of code on its own page, and then re-order the pages for whichever one I want to run at that time.