Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

julia

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.

like image 412
Gnimuc Avatar asked Nov 13 '18 04:11

Gnimuc


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.

like image 100
miguel raz Avatar answered Nov 08 '22 13:11

miguel raz


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

like image 39
Chris Rackauckas Avatar answered Nov 08 '22 15:11

Chris Rackauckas


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.

like image 2
Przemyslaw Szufel Avatar answered Nov 08 '22 14:11

Przemyslaw Szufel


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
like image 2
Bogumił Kamiński Avatar answered Nov 08 '22 14:11

Bogumił Kamiński