Skip to content

Commit

Permalink
Allow any object into the macros, making any extension possible
Browse files Browse the repository at this point in the history
  • Loading branch information
Keluaa committed Jun 15, 2024
1 parent d5779d0 commit 0581cb6
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 26 deletions.
8 changes: 8 additions & 0 deletions docs/src/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@ involves two functions:
- `CodeDiffs.extract_extra_options(f, kwargs)` returns some additional `kwargs` which are passed to `get_code`
- `CodeDiffs.get_code(code_type, f, types; kwargs...)` allows to change `f` depending on its type.
To avoid method ambiguities, do not put type constraints on `code_type`.

Defining a new object type which can be put as an argument to `@code_diff` or `@code_for`
invoves at one function: `CodeDiffs.code_for_diff(obj::YourType; kwargs...)`.
It must return two `String`s, one without and the other without highlighting.
When calling `@code_for obj`, [`code_for_diff(obj)`](@ref) will be called only if `obj` is
not a call expression or a quoted `Expr`.
`kwargs` are the options passed to `@code_for` or the options passed to `@code_diff` for
the side of `obj`.
46 changes: 26 additions & 20 deletions src/compare.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ argconvert(@nospecialize(code_type), arg) = arg
extract_extra_options(@nospecialize(f), _) = (;)
separate_kwargs(code_type::Val, args...; kwargs...) = (argconvert.(Ref(code_type), args), values(kwargs))
is_error_expr(expr) = Base.isexpr(expr, :call) && expr.args[1] in (:error, :throw)
is_call_expr(expr) = Base.isexpr(expr, :call) || (Base.isexpr(expr, :(.), 2) && Base.isexpr(expr.args[2], :tuple))


function gen_code_for_diff_call(mod, expr, diff_options)
# `diff_options` must be already `esc`aped, but not `expr`

if !Base.isexpr(expr, [:call, :(.)])
if !is_call_expr(expr)
error_str = "Expected call (or dot call) to function, got: $expr"
return :(throw(ArgumentError($error_str)))
end
Expand Down Expand Up @@ -173,6 +174,23 @@ function gen_code_for_diff_call(mod, expr, diff_options)
end


function code_diff_for_expr(code_expr, options, mod)
if is_call_expr(code_expr)
# Generic call comparison
return gen_code_for_diff_call(mod, code_expr, options)
else
if Base.isexpr(code_expr, :quote)
# AST comparison
type_guess = (Expr(:kw, :type, QuoteNode(:ast)),)
else
# Mystery comparison
type_guess = ()
end
return :($code_for_diff($(esc(code_expr)); $(type_guess...), $(options...)))
end
end


"""
@code_diff [type=:native] [color=true] [cleanup=true] [option=value...] f₁(...) f₂(...)
@code_diff [option=value...] :(expr₁) :(expr₂)
Expand Down Expand Up @@ -256,17 +274,10 @@ macro code_diff(args...)
code₁ isa QuoteNode && (code₁ = Expr(:quote, Expr(:block, code₁.value)))
code₂ isa QuoteNode && (code₂ = Expr(:quote, Expr(:block, code₂.value)))

if Base.isexpr(code₁, :quote) && Base.isexpr(code₂, :quote)
code₁ = esc(code₁)
code₂ = esc(code₂)
code_for_diff₁ = :($code_for_diff($code₁; type=:ast, $(options₁...)))
code_for_diff₂ = :($code_for_diff($code₂; type=:ast, $(options₂...)))
else
code_for_diff₁ = gen_code_for_diff_call(__module__, code₁, options₁)
code_for_diff₂ = gen_code_for_diff_call(__module__, code₂, options₂)
is_error_expr(code_for_diff₁) && return code_for_diff₁
is_error_expr(code_for_diff₂) && return code_for_diff₂
end
code_for_diff₁ = code_diff_for_expr(code₁, options₁, __module__)
code_for_diff₂ = code_diff_for_expr(code₂, options₂, __module__)
is_error_expr(code_for_diff₁) && return code_for_diff₁
is_error_expr(code_for_diff₂) && return code_for_diff₂

return quote
let
Expand Down Expand Up @@ -342,15 +353,10 @@ macro code_for(args...)
# Simple values such as `:(1)` are stored in a `QuoteNode`
code isa QuoteNode && (code = Expr(:quote, Expr(:block, code.value)))

# Place the diff options in a temp variable in order to extract `io` at runtime
diff_options = esc(gensym(:diff_options))

if Base.isexpr(code, :quote)
code = esc(code)
code_for = :($code_for_diff($code; type=:ast, $(options...)))
else
code_for = gen_code_for_diff_call(__module__, code, [:($diff_options...)])
is_error_expr(code_for) && return code_for
end
code_for = code_diff_for_expr(code, [:($diff_options...)], __module__)
is_error_expr(code_for) && return code_for

return quote
let
Expand Down
12 changes: 6 additions & 6 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -461,15 +461,15 @@ end

@testset "Macros" begin
@testset "error" begin
@test_throws "Expected call" @code_diff "f()" g()
@test_throws "Expected call" @code_diff f() "g()"
@test_throws "Expected call" @code_diff "f()" "g()"
@test_throws "Expected call" @code_diff a b
@test_throws MethodError @code_diff "f()" g()
@test_throws MethodError @code_diff f() "g()"
@test_throws MethodError @code_diff "f()" "g()"
@test_throws UndefVarError @code_diff a b
@test_throws "`key=value`, got: `a + 1`" @code_diff a+1 b c
@test_throws "world age" @code_diff type=:ast world_1=1 f() f()

@test_throws "Expected call" @code_for "f()"
@test_throws "Expected call" @code_for a
@test_throws MethodError @code_for "f()"
@test_throws UndefVarError @code_for a
@test_throws "`key=value`, got: `a + 1`" @code_for a+1 b c
@test_throws "world age" @code_for type=:ast world=1 f()
end
Expand Down

0 comments on commit 0581cb6

Please sign in to comment.