-
Notifications
You must be signed in to change notification settings - Fork 3
/
libcifapi.jl
332 lines (290 loc) · 8.63 KB
/
libcifapi.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
# **Working with libcifapi**
# CIFs are transferred into Julia using the CIFAPI streaming facilities.
# No interface functions are provided for accessing full CIFs.
import Base.Libc:FILE
keep_alive = Any[] #to stop GC freeing memory
"""
This represents the opaque cifapi `cif_tp` type.
"""
mutable struct cif_tp
end
"""A pointer to a `cif_tp` type managed by C"""
mutable struct cif_tp_ptr
handle::Ptr{cif_tp}
end
"""A finalizer for a C-allocated CIF object"""
cif_destroy!(x) = begin
##q = time_ns()
##error_string = "$q: Finalizing CIF object $x"
##t = @task println(error_string)
##schedule(t)
val = ccall((:cif_destroy,libcif),Cint,(Ptr{cif_tp},),x.handle)
if val != 0
error(error_codes[val])
end
end
"""
`cif_handler_tp` holds pointers to functions to be called when
specific CIF structures are encountered during parsing.
"""
struct cif_handler_tp
cif_start::Ptr{Nothing}
cif_end::Ptr{Nothing}
block_start::Ptr{Nothing}
block_end::Ptr{Nothing}
frame_start::Ptr{Nothing}
frame_end::Ptr{Nothing}
loop_start::Ptr{Nothing}
loop_end::Ptr{Nothing}
packet_start::Ptr{Nothing}
packet_end::Ptr{Nothing}
handle_item::Ptr{Nothing}
end
#
# **Data blocks
#
"""
An opaque type representing a CIF block
"""
mutable struct cif_container_tp #cif_block_tp in libcif
end
"""
A pointer to a CIF block or save frame, set by libcif
"""
mutable struct cif_container_tp_ptr
handle::Ptr{cif_container_tp} # *cif_block_tp
end
"""
get_block_code(b::cif_container_tp_ptr)
Return the block code of the referenced container as a string
"""
get_block_code(b::cif_container_tp_ptr) = begin
s = Uchar(0)
val = ccall((:cif_container_get_code,libcif),Cint,(cif_container_tp_ptr,Ptr{Cvoid}),b,Ref(s))
if val != 0
error(error_codes[val])
end
make_jl_string(s)
end
# **CIF values**
"""
The type of data value in a CIF file
"""
mutable struct cif_value_tp
end
"""
A pointer to a data value in a CIF file
"""
mutable struct cif_value_tp_ptr
handle::Ptr{cif_value_tp}
end
# All values returned by libcifapi have a string representation.
Base.String(t::cif_value_tp_ptr) = begin
#Get the textual representation
s = Uchar(0)
val = ccall((:cif_value_get_text,libcif),Cint,(Ptr{cif_value_tp},Ptr{Cvoid}),t.handle,Ref(s))
if val != 0
error(error_codes[val])
end
new_string = make_jl_string(s)
end
# Classify the type. For numbers and strings we leave alone, so that the string
# retrieval above can use the pointer.
"""
get_syntactical_type(t::cif_value_tp_ptr)
Determine the syntactical type of the data value.
"""
get_syntactical_type(t::cif_value_tp_ptr) = begin
val_type = ccall((:cif_value_kind,libcif),Cint,(Ptr{cif_value_tp},),t.handle)
if val_type == 0 || val_type == 1 return typeof(t)
elseif val_type == 2 cif_list
elseif val_type == 3 cif_table
elseif val_type == 4 return Nothing
elseif val_type == 5 return Missing
end
end
# **Loops**
mutable struct cif_loop_tp
end
mutable struct cif_loop_tp_ptr
handle::Ptr{cif_loop_tp}
end
"""
keys(l::Ptr{cif_loop_tp})
List the data names in C data structure `l` corresponding to a CIF loop
"""
Base.keys(l::Ptr{cif_loop_tp}) = begin
ukeys = Uchar_list(0)
val = ccall((:cif_loop_get_names,libcif),Cint,(Ptr{cif_loop_tp},Ptr{Cvoid}),l,Ref(ukeys))
if val != 0
error(error_codes[val])
end
# ukeys will actually be a **UInt16, that is, after return it will hold a pointer to an array of UInt16
if ukeys.strings == C_NULL
error("Unable to get key list address")
end
# Now count how many values we have
n = 1
b = unsafe_load(ukeys.strings,n)
#println("Start of actual array: $(b.string)")
while b.string!=C_NULL
n = n + 1
b = unsafe_load(ukeys.strings,n)
#println("Ptr is $(b.string)")
end
n = n - 1
#println("Number of keys: $n")
# Load in the UChar string pointers
ukey_list = Vector{Uchar}(undef,n)
for j=1:n
ukey_list[j]=unsafe_load(ukeys.strings,j)
end
# Now actually turn them into ordinary strings
key_list = make_jl_string.(ukey_list)
# println("Found loop values $key_list")
return key_list
end
# Loop packets. Only used as a pointer type for the callbacks
struct cif_packet_tp
end
# **Lists and Tables**
# These routines copy lists and tables from C to Julia
cif_list(cv::cif_value_tp_ptr) = begin
cif_type = ccall((:cif_value_kind,libcif),Cint,(Ptr{cif_value_tp},),cv.handle)
if cif_type != 2
error("$val is not a cif list value")
end
elctptr = Ref{Cint}(0)
val = ccall((:cif_value_get_element_count,libcif),Cint,(Ptr{cif_value_tp},Ptr{Cint}),cv.handle,elctptr)
if val != 0
error(error_codes[val])
end
elct = elctptr[]
so_far = Vector()
for el_num in 1:elct
new_element = cif_value_tp_ptr(0)
val = ccall((:cif_value_get_element_at,libcif),Cint,(Ptr{cif_value_tp},Cint,Ptr{cif_value_tp_ptr}),cv.handle,el_num-1,Ref(new_element))
if val != 0
error(error_codes[val])
end
t = get_syntactical_type(new_element)
if t == cif_value_tp_ptr
push!(so_far,String(new_element))
elseif t == cif_list
push!(so_far,cif_list(new_element))
elseif t == cif_table
push!(so_far,cif_table(new_element))
else push!(so_far,t())
end
end
return so_far
end
cif_table(cv::cif_value_tp_ptr) = begin
cif_type = ccall((:cif_value_kind,libcif),Cint,(Ptr{cif_value_tp},),cv.handle)
if cif_type != 3
error("$val is not a cif table value")
end
so_far = Dict{String,Any}()
for el in keys(cv)
new_val = cv[el]
t = get_syntactical_type(new_val)
if t == cif_value_tp_ptr
so_far[el]=String(new_val)
elseif t == cif_list
so_far[el]=cif_list(new_val)
elseif t == cif_table
so_far[el]=cif_table(new_val)
else so_far[el]=t()
end
end
return so_far
end
# The pointer passed to us should point to a table
Base.keys(ct::cif_value_tp_ptr) = begin
ukeys = Uchar_list(0)
#q = time_ns()
#println("$q: accessing keys for $(ct.handle.handle)")
val = ccall((:cif_value_get_keys,libcif),Cint,(Ptr{cif_value_tp},Ptr{Cvoid}),ct.handle,Ref(ukeys))
if val != 0
error(error_codes[val])
end
# ukeys will actually be a **UInt16, that is, after return it will hold a pointer to an array of UInt16
if ukeys.strings == C_NULL
error("Unable to get key list address")
end
# Now count how many values we have
n = 1
b = unsafe_load(ukeys.strings,n)
# println("Start of actual array: $(b.string)")
while b.string!=C_NULL
n = n + 1
b = unsafe_load(ukeys.strings,n)
#println("Ptr is $(b.string)")
end
n = n - 1
#println("Number of keys: $n")
# Load in the UChar string pointers
ukey_list = Vector{Uchar}(undef,n)
for j=1:n
ukey_list[j]=unsafe_load(ukeys.strings,j)
end
# Now actually turn them into ordinary strings
# This is not strictly necessary in the context of iteration
# but will probably help in debugging and error messages
key_list = make_jl_string.(ukey_list)
end
# Access a CIF data value through the C library
Base.getindex(ct::cif_value_tp_ptr,key::AbstractString) = begin
ukey = transcode(UInt16,key)
append!(ukey,0)
new_element = cif_value_tp_ptr(0)
val = ccall((:cif_value_get_item_by_key,libcif),Cint,(Ptr{cif_value_tp},Ptr{UInt16},Ptr{cif_value_tp_ptr}),
ct.handle,ukey,Ref(new_element))
if val == 73
throw(KeyError(key))
end
if val != 0
error(error_codes[val])
end
return new_element
end
"""
The type of external Unicode strings from libicu
"""
mutable struct Uchar
string::Ptr{UInt16}
end
"""
A list of Unicode strings
"""
mutable struct Uchar_list
strings::Ptr{Uchar}
end
# **Utility routines**
"""
Obtain the length of a C null-terminated array `s`.
"""
get_c_length(s::Ptr,max=-1) = begin
# Now loop over the values we have
n = 1
b = unsafe_load(s,n)
while b!=0 && (max == -1 || (max != -1 && n < max))
n = n + 1
b = unsafe_load(s,n)
#println("Char is $b")
end
n = n - 1
#println("Length of string: $n")
return n
end
# TODO: if this is used to make keys for a CIF table,
# we segfault if "own" is true. Why is that, and can
# we fix it
"""
Turn an ICU string into a Jula string
"""
make_jl_string(s::Uchar) = begin
n = get_c_length(s.string,-1) # short for testing
icu_string = unsafe_wrap(Array{UInt16,1},s.string,n,own=false)
block_code = transcode(String,icu_string)
end