Generic function for stripping `LineNumberNode` in `Expr`(should be able to deal with :macrocalls)?

Viewed 471

Is there a build-in Julia function for stripping LineNumberNode in Expr? especially for macrocalls:

julia> ex = :(@foo 1)
:(#= REPL[5]:1 =# @foo 1)

julia> dump(ex)
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @foo
    2: LineNumberNode
      line: Int64 1
      file: Symbol REPL[5]
    3: Int64 1

Tried MacroTools.striplines, but

julia> ex = :(@foo 1+1)
:(#= REPL[7]:1 =# @foo 1 + 1)

julia> MacroTools.striplines(ex) |> dump
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @foo
    2: LineNumberNode
      line: Int64 1
      file: Symbol REPL[7]
    3: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol +
        2: Int64 1
        3: Int64 1

My use-case is to compare two different exprs constructed in different files(so different line number info). My current workaround is to explicitly write Expr(:macrocall, Symbol("@foo"), nothing, :(1+1)) which is a little bit verbose.

4 Answers

The built-in function is Base.remove_linenums!:

julia> ex = quote begin
   x = 3 
   y = 2
   z = 4
   foo(x) = 3
   end
end
quote
    #= REPL[2]:1 =#
    begin
        #= REPL[2]:2 =#
        x = 3
        #= REPL[2]:3 =#
        y = 2
        #= REPL[2]:4 =#
        z = 4
        #= REPL[2]:5 =#
        foo(x) = begin
                #= REPL[2]:5 =#
                3
        end
    end
end

julia> Base.remove_linenums!(ex)
quote
    begin
        x = 3
        y = 2
        z = 4
        foo(x) = begin
                3
        end
    end
end

Credit to Alex Arslan for reminding me of it.

Not built in, but MacroTools.jl has MacroTools.striplines(ex) which removes the LineNumberNodes from an expression.

Since your goal is to be able to compare Exprs maybe replace LineNumberNodes with nothing. This allows to make comparisons and the Exprs still work. See the example below:

julia> macro hello(world)
       println("hello ",world)
       end
@hello (macro with 1 method)

julia> m1 = :(@hello "world")
:(#= REPL[99]:1 =# @hello "world")

julia> m2 = :(@hello "world")
:(#= REPL[100]:1 =# @hello "world")

julia> m1 == m2
false

julia> replace!(arg -> typeof(arg) <: LineNumberNode ? nothing : arg, m1.args);

julia> replace!(arg -> typeof(arg) <: LineNumberNode ? nothing : arg, m2.args);

julia> dump(m1)
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: Symbol @hello
    2: Nothing nothing
    3: String "world"

julia> eval(m1)
hello world

julia> m1 == m2
true

Of course if your code is nested you will have to make the replace its elements recursively over the entire Expr's AST.

You can consider defining the following function to achieve what you want by comparing two expressions for equality ignoring line number nodes:

function cmpexpr(ex1::Expr, ex2::Expr)
    ex1.head === ex2.head || return false
    length(ex1.args) === length(ex2.args) || return false

    for (a1, a2) in zip(ex1.args, ex2.args)
        typeof(a1) === typeof(a2) || return false
        if a1 isa Expr
            cmpexpr(a1, a2) || return false
        elseif !(a1 isa LineNumberNode)
            isequal(a1, a2) || return false
        end
    end
    return true
end
Related