# An escape act in a macro

**URL:** <https://discourse.julialang.org/t/an-escape-act-in-a-macro/63477>\
**Category:** General Usage\
**Created:** [June 24, 2021, 1:11am UTC](https://discourse.julialang.org/t/an-escape-act-in-a-macro/63477 "2021-06-24T01:11:34Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![eliassno](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eliassno/32/18917_2.png) [@eliassno](https://discourse.julialang.org/u/eliassno)\
**Post date:** [June 24, 2021, 1:11am UTC](https://discourse.julialang.org/t/an-escape-act-in-a-macro/63477/1 "2021-06-24T01:11:34Z")

</div>

Warning: Yet another post on hygienic macros.

* * *

I want to annotate the offsets of all interpolated values in a text string (storing the indices of the slices). I found it convenient to do this with a macro, but still struggle to escape the expressions properly.

# MWE

Running the following works:

```julia
span(token, start, stop) = ("text" => token, "start" => start, "stop" => stop)
span(token, start) = span(token, start, start + length(token) - 1)

macro spans(text)
    return quote
        local start = 1
        for arg in $(esc(text.args))
            if !isa(arg, String)
                arg = eval(arg)
                println(span(arg, start)) # Might as well store these spans
            end
            start += length(arg)
        end
        $text
    end
end

question = "the ultimate question of life, universe and everything";
answer = "42";
@spans "The answer to $question is $answer.";

```

> **Output**
>
> ```julia
> ("text" => "the ultimate question of life, universe and everything", "start" => 15, "stop" => 68)
> ("text" => "42", "start" => 73, "stop" => 74)
> "The answer to the ultimate question of life, universe and everything is 42."
> 
> ```

# Issue

After placing this into a separate module, say `module Span`, I managed to replicate the example above by escape the expressions for the interpolated values correctly and immediately ruined that working version 🤦.

This is the code (_identical to the above_) that I’m currently trying to get to work:

```julia
module Spans

    span(token, start, stop) = ("text" => token, "start" => start, "stop" => stop)
    span(token, start) = span(token, start, start + length(token) - 1)

    macro spans(text)
        return quote
            local start = 1
            for arg in $(esc(text.args))
                if !isa(arg, String)
                    arg = eval(arg)
                    println(span(arg, start)) # Might as well store these spans
                end
                start += length(arg)
            end
            $text
        end
    end

end # module

question = "the ultimate question of life, universe and everything";
answer = "42";
Spans.@spans "The answer to $question is $answer." # ERROR: LoadError: UndefVarError: question not defined

```

How do I evaluate `question` and `answer` within the `if` block?

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [June 24, 2021, 6:14am UTC](https://discourse.julialang.org/t/an-escape-act-in-a-macro/63477/2 "2021-06-24T06:14:42Z")

</div>

If you run `@macroexpand Spans.@spans "The answer to $question is $answer."`, you’ll see that `eval` has expanded to `Main.Spans.eval` which causes it to run the evaluation in the wrong module.

There’s a tip somewhere in the manual that says approximately “an `eval` inside of a macro is a sign that you’re doing something wrong”.

One “solution” would be to escape the `eval`, but I would not go down that path…

Instead, I think you need to re-write your macro so that the for-loop runs when the macro runs. (In other words: do not output a quoted for-loop.) Then it will be possible to get the interpolated variables by just escaping the symbols. No need for eval.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [June 24, 2021, 6:42am UTC](https://discourse.julialang.org/t/an-escape-act-in-a-macro/63477/3 "2021-06-24T06:42:01Z")

</div>

This could be a way?

```julia
macro spans(strexp)
    @assert strexp isa Expr && strexp.head == :string

    varindices = findall(x -> !(x isa String), strexp.args)
    quote
        cumsumlengths = 1 .+ [0; cumsum(length.([$(esc.(strexp.args)...)]))]
        starts = cumsumlengths[$varindices]
        stops = cumsumlengths[$varindices.+1] .- 1
        (string = $(esc(strexp)), spans = collect(zip(starts, stops)))
    end
end

question = "the ultimate question of life, universe and everything"
answer = "42"
@spans "The answer to $question is $answer."

```

```julia
(string = "The answer to the ultimate question of life, universe and everything is 42.", spans = [(15, 68), (73, 74)])

```

---

<div class="post-metadata">

**Author:** ![Per](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/per/32/10387_2.png) [@Per](https://discourse.julialang.org/u/Per)\
**Post date:** [June 24, 2021, 7:34am UTC](https://discourse.julialang.org/t/an-escape-act-in-a-macro/63477/4 "2021-06-24T07:34:32Z")

</div>

Yes. That would work.

If one wants to keep the for-loop structure from the original post, that’s also possible. The loop just needs to move outside of the quote. For example:

```julia
macro spans(text)
    lines = []
    for arg in text.args
        if !isa(arg, String)
            push!(lines, :( println(span($(esc(arg)), start)) )) # Might as well store these spans
        end
        push!(lines, :( start += length($(esc(arg))) ))
    end
    return quote
        start = 1
        $(lines...)
        $(esc(text))
    end
end

```
