diff --git a/CHANGES.md b/CHANGES.md index 6fac8b9c6..75fdac370 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) diff --git a/docs/src/other.md b/docs/src/other.md index 5d747bcf6..9f9613d5b 100644 --- a/docs/src/other.md +++ b/docs/src/other.md @@ -13,6 +13,7 @@ DocTestSetup = :(using GAP) @gapwrap @gapattribute @wrap +@install ``` ## Convenience adapters diff --git a/src/julia_to_gap.jl b/src/julia_to_gap.jl index 3a5cdccd2..aa0521018 100644 --- a/src/julia_to_gap.jl +++ b/src/julia_to_gap.jl @@ -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 @@ -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 @@ -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( @@ -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 @@ -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) diff --git a/src/macros.jl b/src/macros.jl index 02a2ad305..229b3cdf1 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -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 diff --git a/test/macros.jl b/test/macros.jl index 7ab4db5e8..c4d64c9d3 100644 --- a/test/macros.jl +++ b/test/macros.jl @@ -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