Macro, loops and variables

Hi everyone,

I have a macro question. It’s probably something about variables hygene as mentioned in this other topic, but I can’t quite figure out, any help would be appreciated

I’m trying to do a macro that wraps around a for loop and does the following:

  1. adds some code before and inside the loop, specifically it creates and updates a Term.jl progressbar.
  2. it puts the loop inside a try/finally statement to correctly stop the progressbar if something goes wrong (otherwise this would cause issues).

I’ve got most of it to work, but if the body of the for loop uses a variable that is defined in the local scope where the loop is done, the macro can’t see it (hence why I think I should put an esc somewhere, but don’t know where).

This is what I’ve got:

macro track(args...)
    quote
        # get pbar number of iterations
        iterobj = $args[1].args[1].args[2]
        N = length(eval(iterobj))

        # get loop expressions
        loop_header = $args[1].args[1]
        loop_body = $args[1].args[2]


        # do loop in try expression to ensure pbar stops
        pbar = nothing  # to have access to it in the try scope
        try
            # start progressbar
            pbar = ProgressBar()
            start!(pbar)
            __pbarjob = addjob!(pbar; N=N)

            # create new expression for loop
            complete = Expr(
                $args[1].head,
                loop_header, 
                Expr(
                    loop_body.head,
                    loop_body.args...,
                    :(sleep(.01)),
                    :(update!($__pbarjob))
                )
            )
            
            # evaluate new expression
            eval(complete)
        finally
            stop!(pbar)
        end
    end
end

complete is an Expr that includes the original loop with an additional line in the body: :(update!($__pbarjob)) to update the progressbar.

Then if I do something like this:

function test()
    @track for i in 1:10
        sleep(0.005)
    end
end

test()

it works and I get a progress bar update at each iteration of the loop:
image

However, if I change test to:

function test()
    x = 0
    @track for i in 1:10
        x += 1
        sleep(0.005)
    end
    println(x)
end

I get:

ERROR: UndefVarError: x not defined
Stacktrace:
 [1] top-level scope
   @ ~/Documents/Github/Term.jl/temp2.jl:48 [inlined]
 [2] top-level scope
   @ ./none:0

Any idea what I should change to make this work?

I guess this is what you want

import Term.progress: track, ProgressBar, update, start, stop
macro track(ex)
    iter = esc(ex.args[1].args[2])
    i = esc(ex.args[1].args[1])
    body = esc(ex.args[2])
    quote
        N = length($iter)
        let pbar = ProgressBar(;N=N)
            try 
                for $i in $iter
                    $body
                    update(pbar)
                end
            finally
                stop(pbar)
            end
        end
        nothing
    end
end

That’s exactly what I needed and so much better than what I was converging upon. I’m still a bit confused about where stuff should happen inside the body of a macro I guess.

The progressbar syntax is a bit different because I’m working on a re-write of progressbars in Term, but this is what it looks like with the progressbar logic inside a try/catch statement:

macro track(ex)
    iter = esc(ex.args[1].args[2])
    i = esc(ex.args[1].args[1])
    body = esc(ex.args[2])
    quote
        pbar = nothing  # needed to access it within the try/catch
        try
            pbar = ProgressBar()
            start!(pbar)
            __pbarjob = addjob!(pbar; N=length($iter))
            for $i in $iter
                update!(__pbarjob) 
                $body
            end
            update!(__pbarjob)
            render(pbar)
        finally 
            stop!(pbar)
        end
        nothing
    end
end

Thank you!

ProgressLogging.jl already has a @progress macro, that supports fancier stuff like multi-variable for loops and comprehensions.
Wouldn’t it make more sense to plug into their API to add support for Term.jl progress bars (the way TerminalLoggers.jl works)?

Thanks for suggesting it. I’ve recently had a long discussion about that. It will happen but it might be worth for Term to have its own interface too.