Script to add "line numbers" to a method

I’ve written a little IDE script to add “line numbers” to a method to aid in debugging, when an error can’t be reproduced within the IDE / debug build.

Paste this into a new IDE Script (File > IDE Scripts > New), save it, then you can use the automatically assigned hotkey to toggle line numbers on and off when doing a build to send out for debugging, MAS review, etc. For critical methods that aren’t super performance sensitive I’ve been leaving the line numbers in place just in case an error is caught (I also log any such error message to a log file).

Anyway, hope this helps someone, it’s been handy for me lately.

  dim lines() as string
  dim newText as string
  dim trimLine as string
  dim addLin as boolean = true
  dim catchBlock as boolean = false
  dim linVar as string = "_lin"
  dim offset as integer = 0
  
  lines = Text.Split(EndOfLine)
  
  for i as integer = 0 to lines.UBound
    if lines(i).Trim().Left(Len(linVar) + 2) = linVar + " =" then
      addLin = false
      exit
    end if
  next
  
  if addLin then
    if lines(0) <> "dim " + linVar + " as integer" then
      newText = "dim " + linVar + " as integer = -1" + EndOfLine
    end if
    for i as integer = 0 to lines.UBound
      trimLine = lines(i).Trim().ReplaceAll(Chr(9), "")
      if trimLine = "dim " + linVar + " as integer = -1" then
        offset = 1
      else
        ' We don't want to modify lin inside a catch block, which is where the
        ' errors should be reported to the user, using lin variable
        if Lowercase(trimLine.Left(5)) = "catch" or Lowercase(trimLine.Left(9)) = "exception" then
          catchBlock = true
        end if
        
        if not catchBlock then
          if trimLine <> "" and trimLine.Left(1) <> "'" and trimLine.Left(2) <> "//" then
            newText = newText + linVar + " = " + Str((i+1-offset)*10) + EndOfLine
          end if
        end if
        
        if Lowercase(trimLine.Left(7)) = "end try" then
          catchBlock = false
        end if
        
        newText = newText + lines(i)
        if i < lines.UBound then newText = newText + EndOfLine
      end if
    next
    Text = newText
    call ShowDialog("Done", linVar + " assignment was added to every other line of the current method. " + EndOfLine + EndOfLine + _
    "Please add an exception clause to the end of the method or wrap it in a try...catch statement, and display the contents of " + linVar + " to aid in debugging." + EndOfLine + _
    "Example:" + EndOfLine + EndOfLine + _
    "' Your code..." + EndOfLine + _
    "' Add to end of method:" + EndOfLine + _
    "exception e as RuntimeException" + EndOfLine + _
    "  if e isa EndException or _" + EndOfLine + _
    "    e isa ThreadEndException then raise e" + EndOfLine + _
    "  MsgBox(""Error at line "" + Str(" + linVar + ") + "": "" + e.Message)" + _
    "" + EndOfLine + EndOfLine + _
    "Run this script again to automatically remove the added assignment statements." _
    , "", "", "", 1)
  else
    for i as integer = 0 to lines.UBound
      trimLine = lines(i).Trim().ReplaceAll(Chr(9), "")
      ' Note: we leave the "dim " + linVar... line in place to not break any added try...catch block that references it
      if trimLine.Left(Len(linVar) + 2) <> linVar + " =" then
        newText = newText + lines(i)
        if i < lines.UBound then newText = newText + EndOfLine
      end if
    next
    Text = newText
  end if

Example in use:

When catching the base class of RuntimeException, you should always include code like this:

catch e as RuntimeException
  if e isa EndException or e isa ThreadEndException then
    raise e
  end if
  
  // Further processing here...

[quote=429156:@Kem Tekinay]When catching the base class of RuntimeException, you should always include code like this:

[code]
catch e as RuntimeException
if e isa EndException or e isa ThreadEndException then
raise e
end if

// Further processing here…
[/code][/quote]

Yes, good point. I’ve edited the script above to add such a check to the example code presented, as well as added support for the Exception statement to not update the line variable.

Suggestion: Instead of updating a line number variable, what it is appended to a stack instead? That way you could see the last line that was updated, but also see where it’s been. For example, if it does or doesn’t enter an IF block sometime before the exception, you’d see that.

Hmm yeah I like that idea, it could be handy for methods that are somewhat less predictable or involve loops. On the other hand the memory requirements could really skyrocket. That said it might be really useful in some cases. The technique definitely can be expanded, the key idea is to make the adding and removing of the extra instrumentation code automatic so that it doesn’t require a lot of work to add/remove/reapply it.

What I would really love is the ability to do all of this just before the code is compiled, so that the user doesn’t even see the extra logic, and of course save some metadata during the build so the script could jump them to the real source code line that is tied to the compiled version’s fake “line numbers”. Not sure there’s any way to do that, though I haven’t looked really extensively.

I wouldn’t want to do that personally. I could see using this script after someone reports a crash when I can’t figure out the section. I’d add this code and send the user a special version to reproduce the problem and report back. Then I’d remove that code.