Debugging

When a program does the wrong thing, the usual reaction is to add printing to it, run it, read the output, and take the printing out again. loft gives you a better tool: stop the program on the line you care about and look around.

The program used below is count.loft. It adds 1, 2 and 3 into a total.

Stop on a line

Say debug, then your file, then a colon and a line number:

  $ printf ':continue\n' | loft debug count.loft:4
  ⏸ paused in main | total = 0, i = 1   (+2 compiler temp(s) — `:vars all`)

The program runs until it reaches line 4 and then waits for you. The line it prints tells you where you are and what your locals hold right now. The note at the end counts the variables the compiler made for itself — the loop's hidden index and a scratch slot — and :vars all shows those too:

  $ printf ':vars all\n:quit\n' | loft debug count.loft:4
  ⏸ paused in main | total = 0, i#index = 1, i = 1, __work_1 = ""

Line 4 is inside a loop that goes round three times, so the program stops there three times. :continue means "carry on until you reach this line again", not "run to the end" — which is why the examples below say :continue more than once when they want the program to finish.

Look at a value

Type the name of a variable and press enter. Any expression works, not just a name — it is worked out where the program is paused, so it can combine the locals you are looking at:

  $ printf 'total + i * 111\n:quit\n' | loft debug count.loft:4
  111

This is the part that replaces printing. You do not have to guess in advance which values you will want; ask for them when you are there.

Move one step at a time

Four commands move the program forward:

The first three only differ when there IS a call, so the program here is steps.loft, whose loop calls add_up(i * 2) on line 9. Stopping on line 9 and stepping goes into it, and the paused line changes function:

  $ printf ':step\n:quit\n' | loft debug steps.loft:9
  ⏸ paused in add_up | n = 2

:next at the same place runs the whole call and stays where you are:

  $ printf ':next\n:quit\n' | loft debug steps.loft:9
  ⏸ paused in main | total = 2, i = 1

The program is now on the for line, about to start the next turn: total already holds this turn's result, and i still holds this turn's value.

And from inside add_up, :finish runs it out and lands back in the caller:

  $ printf ':finish\n:quit\n' | loft debug steps.loft:2
  ⏸ paused in main | total = 0, i = 1

Watch a value instead of watching for it

Re-reading the paused line every time round the loop works, and :watch does it for you: carry on, and the program stops when that value changes, saying what it changed from and to:

  $ printf ':watch total\n:continue\n:quit\n' | loft debug count.loft:4
  watching total — :continue and the run stops when it changes
  ⏯ watchpoint: total changed 0 → 1

Change a value while it is running

You can write to a local, not only read it. This answers "would it work if this were right?" without editing the file and starting again:

  $ printf 'total = 100\n:continue\n:continue\n:continue\n' | loft debug count.loft:4
  total=106
  run finished

The program carries on with the value you gave it. It finished with 106 instead of 6, because the loop still had 2 and 3 to add after the change.

Changing the loop variable moves the loop itself. Set i to 3 on the first turn and that turn adds 3 and is the last one, because the loop counts on from the value you gave it:

  $ printf 'i = 3\n:continue\n' | loft debug count.loft:4
  total=3

:undo takes an edit back, and :redo puts it on again:

  $ printf 'total = 100\n:undo\ntotal + 55\n:quit\n' | loft debug count.loft:4
  55

Getting out

:continue carries on. :quit stops right away. :help lists every command if you forget one — including the short forms :s, :n, :o and :c.

Typing, or feeding it a script

Every example here pipes its commands in, which is how they are checked automatically — the output you see above is compared against what the command really prints. Run loft debug count.loft:4 on its own and you get the (dbg) prompt to type at instead. The commands are the same either way.

fn main() {

This is what count.loft does — the program the examples above debug.

  total = 0;
  for i in 1..4 {
    total += i;
  }
  assert(total == 6, "1 + 2 + 3 is 6, which is what the debugger shows you");
  println("total={total}");
}