/
gap_to_julia.jl
359 lines (318 loc) · 12.2 KB
/
gap_to_julia.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
## Show a specific error on conversion failure.
struct ConversionError <: Base.Exception
obj::Any
jl_type::Any
end
Base.showerror(io::IO, e::ConversionError) =
print(io, "failed to convert $(typeof(e.obj)) to $(e.jl_type):\n $(e.obj)")
"""
RecDict
An internal type of GAP.jl used for tracking conversion results in `gap_to_julia`.
"""
const RecDict = IdDict{Any,Any}
## Conversion from GAP to Julia
"""
gap_to_julia(type, x, recursion_dict::Union{Nothing,RecDict}=nothing; recursive::Bool=true)
Try to convert the object `x` to a Julia object of type `type`.
If `x` is a `GapObj` then the conversion rules are defined in the
manual of the GAP package JuliaInterface.
If `x` is another `GAP.Obj` (for example a `Int64`) then the result is
defined in Julia by `type`.
The parameter `recursion_dict` is used to preserve the identity
of converted subobjects and should never be given by the user.
For GAP lists and records, it makes sense to convert also the subobjects
recursively, or to keep the subobjects as they are;
the behaviour is controlled by `recursive`, which can be `true` or `false`.
# Examples
```jldoctest
julia> GAP.gap_to_julia(GapObj(1//3))
1//3
julia> GAP.gap_to_julia(GapObj("abc"))
"abc"
julia> val = GapObj([ 1 2 ; 3 4 ])
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> GAP.gap_to_julia( val )
2-element Vector{Any}:
Any[1, 2]
Any[3, 4]
julia> GAP.gap_to_julia( val, recursive = false )
2-element Vector{Any}:
GAP: [ 1, 2 ]
GAP: [ 3, 4 ]
julia> GAP.gap_to_julia( Vector{GapObj}, val )
2-element Vector{GapObj}:
GAP: [ 1, 2 ]
GAP: [ 3, 4 ]
julia> GAP.gap_to_julia( Matrix{Int}, val )
2×2 Matrix{Int64}:
1 2
3 4
```
The following `gap_to_julia` conversions are supported by GAP.jl.
(Other Julia packages may provide conversions for more GAP objects.)
| GAP filter | default Julia type | other Julia types |
|---------------|--------------------------|-----------------------|
| `IsInt` | `BigInt` | `T <: Integer |
| `IsFFE` | `GapFFE` | |
| `IsBool` | `Bool` | |
| `IsRat` | `Rational{BigInt}` | `Rational{T} |
| `IsFloat` | `Float64` | `T <: AbstractFloat |
| `IsChar` | `Cuchar` | `Char` |
| `IsStringRep` | `String` | `Symbol`, `Vector{T}` |
| `IsRangeRep` | `StepRange{Int64,Int64}` | `Vector{T}` |
| `IsBListRep` | `BitVector` | `Vector{T}` |
| `IsList` | `Vector{Any}` | `Vector{T}` |
| `IsVectorObj` | `Vector{Any}` | `Vector{T}` |
| `IsMatrixObj` | `Matrix{Any}` | `Matrix{T}` |
| `IsRecord` | `Dict{Symbol, Any}` | `Dict{Symbol, T}` |
"""
function gap_to_julia(t::T, x::Any) where {T<:Type}
## Default for conversion:
## Base case for conversion (least specialized method): Allow converting any
## Julia object x to type T, provided that the type of x is a subtype of T;
## otherwise, explicitly reject the conversion.
## As an example why this is useful, suppose you have a GAP list x (i.e., an
## object of type GapObj) containing a bunch of Julia tuples. Then this method
## enables conversion of that list to a Julia array of type Vector{Tuple},
## like this:
## gap_to_julia(Vector{Tuple{Int64}},xx)
## This works because first the gap_to_julia method with signature
## (::Type{Vector{T}}, :: GapObj) is invoked, with T = Tuple{Int64}; this then
## invokes gap_to_julia recursively with signature (::Tuple{Int64},::Any),
## which ends up selecting the method below.
if !(typeof(x) <: t)
throw(ErrorException(
"Don't know how to convert value of type " *
string(typeof(x)) *
" to type " *
string(t),
))
end
return x
end
## Switch recursion on by default.
## If no method for the given arguments supports 'recursion_dict'
## then assume that it is not needed.
gap_to_julia(type_obj, obj, recursion_dict::Union{Nothing,RecDict}; recursive::Bool = true) =
gap_to_julia(type_obj, obj; recursive)
gap_to_julia(type_obj, obj; recursive::Bool = true) = gap_to_julia(type_obj, obj)
## Default
gap_to_julia(::Type{Any}, x::GapObj; recursive::Bool = true) =
gap_to_julia(x; recursive)
gap_to_julia(::Type{Any}, x::Any) = x
gap_to_julia(::T, x::Nothing) where {T<:Type} = nothing
gap_to_julia(::Type{Any}, x::Nothing) = nothing
## Handle "conversion" to GAP.Obj and GapObj (may occur in recursions).
gap_to_julia(::Type{Obj}, x::Obj) = x
gap_to_julia(::Type{GapObj}, x::GapObj) = x
## Integers
gap_to_julia(::Type{T}, x::GapInt) where {T<:Integer} = T(x)
## Rationals
gap_to_julia(::Type{Rational{T}}, x::GapInt) where {T<:Integer} = Rational{T}(x)
## Floats
gap_to_julia(::Type{T}, obj::GapObj) where {T<:AbstractFloat} = T(obj)
## Chars
gap_to_julia(::Type{Char}, obj::GapObj) = Char(obj)
gap_to_julia(::Type{Cuchar}, obj::GapObj) = Cuchar(obj)
## Strings
gap_to_julia(::Type{String}, obj::GapObj) = String(obj)
## Symbols
gap_to_julia(::Type{Symbol}, obj::GapObj) = Symbol(obj)
## Convert GAP string to Vector{UInt8}
function gap_to_julia(::Type{Vector{UInt8}}, obj::GapObj)
Wrappers.IsStringRep(obj) && return CSTR_STRING_AS_ARRAY(obj)
Wrappers.IsList(obj) && return UInt8[gap_to_julia(UInt8, obj[i]) for i = 1:length(obj)]
throw(ConversionError(obj, Vector{UInt8}))
end
## BitVectors
gap_to_julia(::Type{BitVector}, obj::GapObj) = BitVector(obj)
## Vectors
function gap_to_julia(
::Type{Vector{T}},
obj::GapObj,
recursion_dict::RecDict = IdDict();
recursive::Bool = true,
) where {T}
if Wrappers.IsList(obj)
islist = true
elseif Wrappers.IsVectorObj(obj)
islist = false
ELM_LIST = Wrappers.ELM_LIST
else
throw(ConversionError(obj, Vector{T}))
end
if !haskey(recursion_dict, obj)
len_list = length(obj)
new_array = Vector{T}(undef, len_list)
recursion_dict[obj] = new_array
for i = 1:len_list
if islist
current_obj = ElmList(obj, i) # returns 'nothing' for holes in the list
else
# vector objects aren't lists,
# but the function for accessing entries is `ELM_LIST`
current_obj = ELM_LIST(obj, i)
end
if recursive && !isbitstype(typeof(current_obj))
new_array[i] = get!(recursion_dict, current_obj) do
gap_to_julia(T, current_obj, recursion_dict; recursive = true)
end
else
new_array[i] = current_obj
end
end
end
return recursion_dict[obj]::Vector{T}
end
## Matrices or lists of lists
function gap_to_julia(
type::Type{Matrix{T}},
obj::GapObj,
recursion_dict::RecDict = IdDict();
recursive::Bool = true,
) where {T}
if haskey(recursion_dict, obj)
return recursion_dict[obj]::Matrix{T}
end
if Wrappers.IsMatrixObj(obj)
nrows = Wrappers.NumberRows(obj)::Int
ncols = Wrappers.NumberColumns(obj)::Int
elseif Wrappers.IsList(obj)
nrows = length(obj)
ncols = nrows == 0 ? 0 : length(obj[1])
else
throw(ConversionError(obj, type))
end
elm = Wrappers.ELM_MAT
new_array = type(undef, nrows, ncols)
if recursive
recursion_dict[obj] = new_array
end
for i = 1:nrows
for j = 1:ncols
current_obj = elm(obj, i, j)
if recursive
new_array[i, j] = get!(recursion_dict, current_obj) do
gap_to_julia(T, current_obj, recursion_dict; recursive = true)
end
else
new_array[i, j] = current_obj
end
end
end
return new_array::Matrix{T}
end
## Sets
## Assume that this function cannot be called inside recursions.
## Note that Julia does not support `Set{Set{Int}}([[1], [1, 1]])`.
## Without this assumption, we would have to construct the set
## in the beginning, and then fill it up using `union!`.
function gap_to_julia(::Type{Set{T}}, obj::GapObj; recursive::Bool = true) where {T}
if Wrappers.IsCollection(obj)
obj = Wrappers.AsSet(obj)
elseif Wrappers.IsList(obj)
# The list entries may be not comparable via `<`.
obj = Wrappers.DuplicateFreeList(obj)
else
throw(ConversionError(obj, Set{T}))
end
len_list = Wrappers.Length(obj)::Int
new_array = Vector{T}(undef, len_list)
if recursive
recursion_dict = IdDict()
end
for i = 1:len_list
current_obj = ElmList(obj, i)
if recursive
new_array[i] = get!(recursion_dict, current_obj) do
gap_to_julia(T, current_obj, recursion_dict; recursive = true)
end
else
new_array[i] = current_obj
end
end
return Set{T}(new_array)
end
## Tuples
## Note that the tuple type prescribes the types of the entries,
## thus we have to convert at least also the next layer,
## even if `recursive == false` holds.
function gap_to_julia(
::Type{T},
obj::GapObj,
recursion_dict::RecDict = IdDict();
recursive::Bool = true,
) where {T<:Tuple}
!Wrappers.IsList(obj) && throw(ConversionError(obj, T))
if !haskey(recursion_dict, obj)
parameters = T.parameters
len = length(parameters)
Wrappers.Length(obj) == len ||
throw(ArgumentError("length of $obj does not match type $T"))
list = [
gap_to_julia(parameters[i], obj[i], recursion_dict; recursive)
for i = 1:len
]
recursion_dict[obj] = T(list)
end
return recursion_dict[obj]::T
end
## Ranges
gap_to_julia(::Type{T}, obj::GapObj) where {T<:UnitRange} = T(obj)
gap_to_julia(::Type{T}, obj::GapObj) where {T<:StepRange} = T(obj)
## Dictionaries
function gap_to_julia(
::Type{Dict{Symbol,T}},
obj::GapObj,
recursion_dict::RecDict = IdDict();
recursive::Bool = true,
) where {T}
!Wrappers.IsRecord(obj) && throw(ConversionError(obj, Dict{Symbol,T}))
if !haskey(recursion_dict, obj)
names = Wrappers.RecNames(obj)
names_list = Vector{Symbol}(names)
dict = Dict{Symbol,T}()
recursion_dict[obj] = dict
for key in names_list
current_obj = getproperty(obj, key)
if recursive
dict[key] = get!(recursion_dict, current_obj) do
gap_to_julia(T, current_obj, recursion_dict; recursive = true)
end
else
dict[key] = current_obj
end
end
end
return recursion_dict[obj]::Dict{Symbol,T}
end
## Generic conversions
gap_to_julia(x::Any) = x
function gap_to_julia(x::GapObj; recursive::Bool = true)
GAP_IS_INT(x) && return gap_to_julia(BigInt, x)
GAP_IS_RAT(x) && return gap_to_julia(Rational{BigInt}, x)
GAP_IS_MACFLOAT(x) && return gap_to_julia(Float64, x)
GAP_IS_CHAR(x) && return gap_to_julia(Cuchar, x)
# Do not choose this conversion for other lists in 'IsString'.
Wrappers.IsStringRep(x) && return gap_to_julia(String, x)
# Do not choose this conversion for other lists in 'IsRange'.
Wrappers.IsRangeRep(x) && return gap_to_julia(StepRange{Int64,Int64}, x)
# Do not choose this conversion for other lists in 'IsBlist'.
Wrappers.IsBlistRep(x) && return gap_to_julia(BitVector, x)
Wrappers.IsList(x) && return gap_to_julia(Vector{Any}, x; recursive)
Wrappers.IsMatrixObj(x) && return gap_to_julia(Matrix{Any}, x; recursive)
Wrappers.IsVectorObj(x) && return gap_to_julia(Vector{Any}, x; recursive)
Wrappers.IsRecord(x) && return gap_to_julia(Dict{Symbol,Any}, x; recursive)
Wrappers.IS_JULIA_FUNC(x) && return UnwrapJuliaFunc(x)
throw(ConversionError(x, "any known type"))
end
## for the GAP function GAPToJulia:
## turning arguments into keyword arguments is easier in Julia than in GAP
_gap_to_julia(x::Obj) = gap_to_julia(x)
_gap_to_julia(x::Bool, recursive::Bool) = x
_gap_to_julia(x::Int, recursive::Bool) = x
_gap_to_julia(x::FFE, recursive::Bool) = x
_gap_to_julia(x::GapObj, recursive::Bool) = gap_to_julia(x; recursive)
_gap_to_julia(::Type{T}, x::Obj) where {T} = gap_to_julia(T, x)
_gap_to_julia(::Type{T}, x::Obj, recursive::Bool) where {T} =
gap_to_julia(T, x; recursive)