Skip to content

Commit

Permalink
introduce the macro GAP.@install
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasBreuer committed Sep 3, 2024
1 parent f279fb0 commit d43680b
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 23 deletions.
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
In this situation, one can now use the environment variable
`JULIA_STARTUP_FILE_IN_GAP`;
if its value is `yes` then the startup file gets included, otherwise not.
- Rewrite `julia_to_gap`, in order to
- make the installation of new conversion methods from Julia to GAP
simpler and safer and
- restrict the necessity to create dictionaries to situations where
recursive conversions make sense.
For that, the function `julia_to_gap_internal`, the macro `GAP.@install`,
and the type `GapCacheDict` were introduced.

## Version 0.11.1 (released 2024-06-06)

Expand Down
1 change: 1 addition & 0 deletions docs/src/other.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ DocTestSetup = :(using GAP)
@gapwrap
@gapattribute
@wrap
@install
```

## Convenience adapters
Expand Down
45 changes: 22 additions & 23 deletions src/julia_to_gap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ function julia_to_gap end
# default
julia_to_gap(x, cache::GapCacheDict = nothing; recursive::Bool = false) = julia_to_gap_internal(x, cache, recursive)

julia_to_gap_internal(x::FFE, cache::GapCacheDict, recursive::Bool) = x # Default for actual GAP objects is to do nothing
julia_to_gap_internal(x::Bool, cache::GapCacheDict, recursive::Bool) = x # Default for actual GAP objects is to do nothing
# The calls to `GAP.@install` install methods for `julia_to_gap_internal`
function julia_to_gap_internal end

GAP.@install GapObj(x::FFE) = x # Default for actual GAP objects is to do nothing
GAP.@install GapObj(x::Bool) = x # Default for actual GAP objects is to do nothing

## Integers: general case first deal with things that fit into immediate
## integers, then falls back to converting to BigInt and calling into the GAP
Expand All @@ -68,21 +71,21 @@ end

## Small integers types always fit into GAP immediate integers, and thus are
## represented by Int64 on the Julia side.
julia_to_gap_internal(x::Int64, cache::GapCacheDict, recursive::Bool) = x
julia_to_gap_internal(x::Int32, cache::GapCacheDict, recursive::Bool) = Int64(x)
julia_to_gap_internal(x::Int16, cache::GapCacheDict, recursive::Bool) = Int64(x)
julia_to_gap_internal(x::Int8, cache::GapCacheDict, recursive::Bool) = Int64(x)
julia_to_gap_internal(x::UInt32, cache::GapCacheDict, recursive::Bool) = Int64(x)
julia_to_gap_internal(x::UInt16, cache::GapCacheDict, recursive::Bool) = Int64(x)
julia_to_gap_internal(x::UInt8, cache::GapCacheDict, recursive::Bool) = Int64(x)
GAP.@install GapObj(x::Int64) = x
GAP.@install GapObj(x::Int32) = Int64(x)
GAP.@install GapObj(x::Int16) = Int64(x)
GAP.@install GapObj(x::Int8) = Int64(x)
GAP.@install GapObj(x::UInt32) = Int64(x)
GAP.@install GapObj(x::UInt16) = Int64(x)
GAP.@install GapObj(x::UInt8) = Int64(x)

function julia_to_gap_internal(x::UInt, cache::GapCacheDict, recursive::Bool)
GAP.@install function GapObj(x::UInt)
x < (1<<60) && return Int64(x)
return ccall((:ObjInt_UInt, libgap), GapObj, (UInt64, ), x)
end

## BigInts are converted via a ccall
function julia_to_gap_internal(x::BigInt, cache::GapCacheDict, recursive::Bool)
GAP.@install function GapObj(x::BigInt)
x in -1<<60:(1<<60-1) && return Int64(x)
return GC.@preserve x ccall((:MakeObjInt, libgap), GapObj, (Ptr{UInt64}, Cint), x.d, x.size)
end
Expand All @@ -104,20 +107,16 @@ function julia_to_gap_internal(x::Rational{T}, cache::GapCacheDict, recursive::B
end

## Floats
julia_to_gap_internal(x::Float64, cache::GapCacheDict, recursive::Bool) = NEW_MACFLOAT(x)
julia_to_gap_internal(x::Float32, cache::GapCacheDict, recursive::Bool) = NEW_MACFLOAT(Float64(x))
julia_to_gap_internal(x::Float16, cache::GapCacheDict, recursive::Bool) = NEW_MACFLOAT(Float64(x))
GAP.@install GapObj(x::Float64) = NEW_MACFLOAT(x)
GAP.@install GapObj(x::Float32) = NEW_MACFLOAT(Float64(x))
GAP.@install GapObj(x::Float16) = NEW_MACFLOAT(Float64(x))

## Chars
julia_to_gap_internal(x::Char, cache::GapCacheDict, recursive::Bool) = CharWithValue(Cuchar(x))
GAP.@install GapObj(x::Char) = CharWithValue(Cuchar(x))

## Strings and symbols
julia_to_gap_internal(x::AbstractString, cache::GapCacheDict, recursive::Bool) = MakeString(string(x))
julia_to_gap_internal(x::Symbol, cache::GapCacheDict, recursive::Bool) = MakeString(string(x))

# ## Generic caller for optional arguments
# julia_to_gap(obj::Any; recursive::Bool) = julia_to_gap(obj)
# julia_to_gap(obj::Any, recursion_dict::IdDict{Any,Any}; recursive::Bool = true) = julia_to_gap(obj)
GAP.@install GapObj(x::AbstractString) = MakeString(string(x))
GAP.@install GapObj(x::Symbol) = MakeString(string(x))

## Arrays (including BitVector)
function julia_to_gap_internal(
Expand Down Expand Up @@ -186,7 +185,7 @@ function julia_to_gap_internal(
end

## Ranges
function julia_to_gap_internal(r::AbstractRange{<:Integer}, recursion_dict::GapCacheDict, recursive::Bool)
GAP.@install function GapObj(r::AbstractRange{<:Integer})
res = NewRange(length(r), first(r), step(r))
Wrappers.IsRange(res) || throw(ConversionError(r, GapObj))
return res
Expand Down Expand Up @@ -258,4 +257,4 @@ function julia_to_gap_internal(
return ret_val
end

julia_to_gap_internal(func::Function, recursion_dict::GapCacheDict, recursive::Bool) = WrapJuliaFunc(func)
GAP.@install GapObj(func::Function) = WrapJuliaFunc(func)
44 changes: 44 additions & 0 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,47 @@ macro wrap(ex)
Base.@__doc__ $(Expr(:call, name, lhsargs...)) = $body
end)
end

"""
@install
When applied to a unary method definition for the function `GapObj`,
with argument of type `T`,
this macro installs instead a three argument method for
`GAP.julia_to_gap_internal`, with second argument of type
`GAP.GapCacheDict` and third argument of type `Bool`.
This way, the intended `GapObj(x::T)` method becomes available,
and additionally its code is applicable in recursive calls,
for example when `GapObj` is called with a vector of objects of type `T`.
The calls of the macro have the form `GAP.@install GapObj(x::T) = f(x)`
or `GAP.@install function GapObj(x::T) ... end`.
"""
macro install(ex)
errmsg = "GAP.@install must be applied to a unary method definition for GapObj"

# split the method definition
def_dict = try
MacroTools.splitdef(ex)
catch
error(errmsg)
end
def_dict[:name] === :GapObj || throw(ArgumentError(errmsg))
length(def_dict[:args]) == 1 || throw(ArgumentError(errmsg))

# extend the arguments list of the function
push!(def_dict[:args], :(cache::GAP.GapCacheDict))
push!(def_dict[:args], :(recursive::Bool))

# replace the function name
def_dict[:name] = :(GAP.julia_to_gap_internal)

# assemble the method definition again
ex = MacroTools.combinedef(def_dict)

return esc(Expr(
:block,
:(Base.@__doc__ $ex),
))
end
12 changes: 12 additions & 0 deletions test/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,15 @@ end
@test_throws ErrorException g"\\"

end

@testset "@install GapObj" begin
a = GAP.evalstr("(1,2)")

struct TestType1 X::GapObj end
GAP.@install GapObj(x::TestType1) = x.X
@test GapObj(TestType1(a)) === a

struct TestType2 X::GapObj end
GAP.@install function GapObj(x::TestType2) return x.X; end
@test GapObj(TestType2(a)) === a
end

0 comments on commit d43680b

Please sign in to comment.