rubydex 0.1.0.beta1-x86_64-linux → 0.1.0.beta2-x86_64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/ext/rubydex/declaration.c +146 -0
  3. data/ext/rubydex/declaration.h +10 -0
  4. data/ext/rubydex/definition.c +234 -0
  5. data/ext/rubydex/definition.h +28 -0
  6. data/ext/rubydex/diagnostic.c +6 -0
  7. data/ext/rubydex/diagnostic.h +11 -0
  8. data/ext/rubydex/document.c +98 -0
  9. data/ext/rubydex/document.h +10 -0
  10. data/ext/rubydex/extconf.rb +36 -15
  11. data/ext/rubydex/graph.c +405 -0
  12. data/ext/rubydex/graph.h +10 -0
  13. data/ext/rubydex/handle.h +44 -0
  14. data/ext/rubydex/location.c +22 -0
  15. data/ext/rubydex/location.h +15 -0
  16. data/ext/rubydex/reference.c +104 -0
  17. data/ext/rubydex/reference.h +16 -0
  18. data/ext/rubydex/rubydex.c +22 -0
  19. data/ext/rubydex/utils.c +27 -0
  20. data/ext/rubydex/utils.h +13 -0
  21. data/lib/rubydex/3.2/rubydex.so +0 -0
  22. data/lib/rubydex/3.3/rubydex.so +0 -0
  23. data/lib/rubydex/3.4/rubydex.so +0 -0
  24. data/lib/rubydex/4.0/rubydex.so +0 -0
  25. data/lib/rubydex/librubydex_sys.so +0 -0
  26. data/lib/rubydex/version.rb +1 -1
  27. data/rust/Cargo.lock +1275 -0
  28. data/rust/Cargo.toml +23 -0
  29. data/rust/about.hbs +78 -0
  30. data/rust/about.toml +9 -0
  31. data/rust/rubydex/Cargo.toml +41 -0
  32. data/rust/rubydex/src/diagnostic.rs +108 -0
  33. data/rust/rubydex/src/errors.rs +28 -0
  34. data/rust/rubydex/src/indexing/local_graph.rs +172 -0
  35. data/rust/rubydex/src/indexing/ruby_indexer.rs +5397 -0
  36. data/rust/rubydex/src/indexing.rs +128 -0
  37. data/rust/rubydex/src/job_queue.rs +186 -0
  38. data/rust/rubydex/src/lib.rs +15 -0
  39. data/rust/rubydex/src/listing.rs +249 -0
  40. data/rust/rubydex/src/main.rs +116 -0
  41. data/rust/rubydex/src/model/comment.rs +24 -0
  42. data/rust/rubydex/src/model/declaration.rs +541 -0
  43. data/rust/rubydex/src/model/definitions.rs +1475 -0
  44. data/rust/rubydex/src/model/document.rs +111 -0
  45. data/rust/rubydex/src/model/encoding.rs +22 -0
  46. data/rust/rubydex/src/model/graph.rs +1387 -0
  47. data/rust/rubydex/src/model/id.rs +90 -0
  48. data/rust/rubydex/src/model/identity_maps.rs +54 -0
  49. data/rust/rubydex/src/model/ids.rs +32 -0
  50. data/rust/rubydex/src/model/name.rs +188 -0
  51. data/rust/rubydex/src/model/references.rs +129 -0
  52. data/rust/rubydex/src/model/string_ref.rs +44 -0
  53. data/rust/rubydex/src/model/visibility.rs +41 -0
  54. data/rust/rubydex/src/model.rs +13 -0
  55. data/rust/rubydex/src/offset.rs +70 -0
  56. data/rust/rubydex/src/position.rs +6 -0
  57. data/rust/rubydex/src/query.rs +103 -0
  58. data/rust/rubydex/src/resolution.rs +4421 -0
  59. data/rust/rubydex/src/stats/memory.rs +71 -0
  60. data/rust/rubydex/src/stats/timer.rs +126 -0
  61. data/rust/rubydex/src/stats.rs +9 -0
  62. data/rust/rubydex/src/test_utils/context.rs +226 -0
  63. data/rust/rubydex/src/test_utils/graph_test.rs +229 -0
  64. data/rust/rubydex/src/test_utils/local_graph_test.rs +166 -0
  65. data/rust/rubydex/src/test_utils.rs +52 -0
  66. data/rust/rubydex/src/visualization/dot.rs +176 -0
  67. data/rust/rubydex/src/visualization.rs +6 -0
  68. data/rust/rubydex/tests/cli.rs +167 -0
  69. data/rust/rubydex-sys/Cargo.toml +20 -0
  70. data/rust/rubydex-sys/build.rs +14 -0
  71. data/rust/rubydex-sys/cbindgen.toml +12 -0
  72. data/rust/rubydex-sys/src/declaration_api.rs +114 -0
  73. data/rust/rubydex-sys/src/definition_api.rs +350 -0
  74. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -0
  75. data/rust/rubydex-sys/src/document_api.rs +54 -0
  76. data/rust/rubydex-sys/src/graph_api.rs +493 -0
  77. data/rust/rubydex-sys/src/lib.rs +9 -0
  78. data/rust/rubydex-sys/src/location_api.rs +79 -0
  79. data/rust/rubydex-sys/src/name_api.rs +81 -0
  80. data/rust/rubydex-sys/src/reference_api.rs +191 -0
  81. data/rust/rubydex-sys/src/utils.rs +50 -0
  82. data/rust/rustfmt.toml +2 -0
  83. metadata +77 -2
@@ -0,0 +1,493 @@
1
+ //! This file provides the C API for the Graph object
2
+
3
+ use crate::reference_api::{ReferenceKind, ReferencesIter};
4
+ use crate::{name_api, utils};
5
+ use libc::{c_char, c_void};
6
+ use rubydex::model::encoding::Encoding;
7
+ use rubydex::model::graph::Graph;
8
+ use rubydex::model::ids::DeclarationId;
9
+ use rubydex::resolution::Resolver;
10
+ use rubydex::{indexing, listing, query};
11
+ use std::ffi::CString;
12
+ use std::{mem, ptr};
13
+
14
+ pub type GraphPointer = *mut c_void;
15
+
16
+ /// Creates a new graph within a mutex. This is meant to be used when creating new Graph objects in Ruby
17
+ #[unsafe(no_mangle)]
18
+ pub extern "C" fn rdx_graph_new() -> GraphPointer {
19
+ Box::into_raw(Box::new(Graph::new())) as GraphPointer
20
+ }
21
+
22
+ /// Frees a Graph through its pointer
23
+ #[unsafe(no_mangle)]
24
+ pub extern "C" fn rdx_graph_free(pointer: GraphPointer) {
25
+ unsafe {
26
+ let _ = Box::from_raw(pointer.cast::<Graph>());
27
+ }
28
+ }
29
+
30
+ pub fn with_graph<F, T>(pointer: GraphPointer, action: F) -> T
31
+ where
32
+ F: FnOnce(&mut Graph) -> T,
33
+ {
34
+ let mut graph = unsafe { Box::from_raw(pointer.cast::<Graph>()) };
35
+ let result = action(&mut graph);
36
+ mem::forget(graph);
37
+ result
38
+ }
39
+
40
+ /// Searches the graph based on query and returns all declarations that match
41
+ ///
42
+ /// # Safety
43
+ ///
44
+ /// Expects both the graph and the query pointers to be valid
45
+ #[unsafe(no_mangle)]
46
+ pub unsafe extern "C" fn rdx_graph_declarations_search(
47
+ pointer: GraphPointer,
48
+ c_query: *const c_char,
49
+ ) -> *mut DeclarationsIter {
50
+ let Ok(query) = (unsafe { utils::convert_char_ptr_to_string(c_query) }) else {
51
+ return ptr::null_mut();
52
+ };
53
+
54
+ let ids = with_graph(pointer, |graph| {
55
+ query::declaration_search(graph, &query)
56
+ .into_iter()
57
+ .map(|id| *id)
58
+ .collect::<Vec<i64>>()
59
+ .into_boxed_slice()
60
+ });
61
+
62
+ Box::into_raw(Box::new(DeclarationsIter { ids, index: 0 }))
63
+ }
64
+
65
+ /// # Panics
66
+ ///
67
+ /// Will panic if the nesting cannot be transformed into a vector of strings
68
+ ///
69
+ /// # Safety
70
+ ///
71
+ /// Assumes that the `const_name` and `nesting` pointer are valid
72
+ #[unsafe(no_mangle)]
73
+ pub unsafe extern "C" fn rdx_graph_resolve_constant(
74
+ pointer: GraphPointer,
75
+ const_name: *const c_char,
76
+ nesting: *const *const c_char,
77
+ count: usize,
78
+ ) -> *const i64 {
79
+ with_graph(pointer, |graph| {
80
+ let nesting: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(nesting, count).unwrap() };
81
+ let const_name: String = unsafe { utils::convert_char_ptr_to_string(const_name).unwrap() };
82
+ let (name_id, names_to_untrack) = name_api::nesting_stack_to_name_id(graph, &const_name, nesting);
83
+
84
+ let mut resolver = Resolver::new(graph);
85
+
86
+ let resolved_id = match resolver.resolve_constant(name_id) {
87
+ Some(id) => Box::into_raw(Box::new(*id)).cast_const(),
88
+ None => ptr::null(),
89
+ };
90
+
91
+ for name_id in names_to_untrack {
92
+ graph.untrack_name(name_id);
93
+ }
94
+ resolved_id
95
+ })
96
+ }
97
+
98
+ /// Indexes all given file paths in parallel using the provided Graph pointer
99
+ ///
100
+ /// # Panics
101
+ ///
102
+ /// Will panic if the given array of C string file paths cannot be converted to a Vec<String>
103
+ ///
104
+ /// # Safety
105
+ ///
106
+ /// This function is unsafe because it dereferences raw pointers coming from C. The caller has to ensure that the Ruby
107
+ /// VM will not free the pointers related to the string array while they are in use by Rust
108
+ #[unsafe(no_mangle)]
109
+ pub unsafe extern "C" fn rdx_index_all(
110
+ pointer: GraphPointer,
111
+ file_paths: *const *const c_char,
112
+ count: usize,
113
+ ) -> *const c_char {
114
+ let file_paths: Vec<String> = unsafe { utils::convert_double_pointer_to_vec(file_paths, count).unwrap() };
115
+ let (file_paths, errors) = listing::collect_file_paths(file_paths);
116
+
117
+ if !errors.is_empty() {
118
+ let error_messages = errors
119
+ .iter()
120
+ .map(std::string::ToString::to_string)
121
+ .collect::<Vec<_>>()
122
+ .join("\n");
123
+
124
+ return CString::new(error_messages).unwrap().into_raw().cast_const();
125
+ }
126
+
127
+ with_graph(pointer, |graph| {
128
+ let errors = indexing::index_files(graph, file_paths);
129
+
130
+ if !errors.is_empty() {
131
+ let error_messages = errors
132
+ .iter()
133
+ .map(std::string::ToString::to_string)
134
+ .collect::<Vec<_>>()
135
+ .join("\n");
136
+
137
+ return CString::new(error_messages).unwrap().into_raw().cast_const();
138
+ }
139
+
140
+ ptr::null()
141
+ })
142
+ }
143
+
144
+ /// Runs the resolver to compute declarations, ownership and related structures
145
+ #[unsafe(no_mangle)]
146
+ pub extern "C" fn rdx_graph_resolve(pointer: GraphPointer) {
147
+ with_graph(pointer, |graph| {
148
+ let mut resolver = Resolver::new(graph);
149
+ resolver.resolve_all();
150
+ });
151
+ }
152
+
153
+ /// # Safety
154
+ ///
155
+ /// Expects both the graph pointer and encoding string pointer to be valid
156
+ #[unsafe(no_mangle)]
157
+ pub unsafe extern "C" fn rdx_graph_set_encoding(pointer: GraphPointer, encoding_str: *const c_char) -> bool {
158
+ let Ok(encoding) = (unsafe { utils::convert_char_ptr_to_string(encoding_str) }) else {
159
+ return false;
160
+ };
161
+
162
+ let encoding_variant = match encoding.as_str() {
163
+ "utf8" => Encoding::Utf8,
164
+ "utf16" => Encoding::Utf16,
165
+ "utf32" => Encoding::Utf32,
166
+ _ => {
167
+ return false;
168
+ }
169
+ };
170
+
171
+ with_graph(pointer, |graph| {
172
+ graph.set_encoding(encoding_variant);
173
+ });
174
+
175
+ true
176
+ }
177
+
178
+ /// An iterator over declaration IDs
179
+ ///
180
+ /// We snapshot the IDs at iterator creation so if the graph is modified, the iterator will not see the changes
181
+ #[derive(Debug)]
182
+ pub struct DeclarationsIter {
183
+ /// The snapshot of declaration IDs
184
+ ids: Box<[i64]>,
185
+ /// The current index of the iterator
186
+ index: usize,
187
+ }
188
+
189
+ /// Creates a new iterator over declaration IDs by snapshotting the current set of IDs.
190
+ ///
191
+ /// # Safety
192
+ ///
193
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
194
+ /// - The returned pointer must be freed with `rdx_graph_declarations_iter_free`.
195
+ ///
196
+ /// # Panics
197
+ ///
198
+ /// Will panic if acquiring a read lock on the graph's declarations fails
199
+ #[unsafe(no_mangle)]
200
+ pub unsafe extern "C" fn rdx_graph_declarations_iter_new(pointer: GraphPointer) -> *mut DeclarationsIter {
201
+ // Snapshot the IDs at iterator creation to avoid borrowing across FFI calls
202
+ let ids = with_graph(pointer, |graph| {
203
+ graph
204
+ .declarations()
205
+ .keys()
206
+ .map(|name_id| **name_id)
207
+ .collect::<Vec<i64>>()
208
+ .into_boxed_slice()
209
+ });
210
+
211
+ Box::into_raw(Box::new(DeclarationsIter { ids, index: 0 }))
212
+ }
213
+
214
+ /// Returns the total number of IDs in the iterator snapshot.
215
+ ///
216
+ /// # Safety
217
+ ///
218
+ /// - `iter` must be a valid pointer previously returned by `rdx_graph_declarations_iter_new`.
219
+ #[unsafe(no_mangle)]
220
+ pub unsafe extern "C" fn rdx_graph_declarations_iter_len(iter: *const DeclarationsIter) -> usize {
221
+ if iter.is_null() {
222
+ return 0;
223
+ }
224
+
225
+ unsafe { (&*iter).ids.len() }
226
+ }
227
+
228
+ /// Advances the iterator and writes the next ID into `out_id`.
229
+ /// Returns `true` if an ID was written, or `false` if the iterator is exhausted or inputs are invalid.
230
+ ///
231
+ /// # Safety
232
+ ///
233
+ /// - `iter` must be a valid pointer previously returned by `rdx_graph_declarations_iter_new`.
234
+ /// - `out_id` must be a valid, writable pointer.
235
+ #[unsafe(no_mangle)]
236
+ pub unsafe extern "C" fn rdx_graph_declarations_iter_next(iter: *mut DeclarationsIter, out_id: *mut i64) -> bool {
237
+ if iter.is_null() || out_id.is_null() {
238
+ return false;
239
+ }
240
+
241
+ let it = unsafe { &mut *iter };
242
+ if it.index >= it.ids.len() {
243
+ return false;
244
+ }
245
+
246
+ let id = it.ids[it.index];
247
+ it.index += 1;
248
+ unsafe { *out_id = id };
249
+
250
+ true
251
+ }
252
+
253
+ /// Frees an iterator created by `rdx_graph_declarations_iter_new`.
254
+ ///
255
+ /// # Safety
256
+ ///
257
+ /// - `iter` must be a pointer previously returned by `rdx_graph_declarations_iter_new`.
258
+ /// - `iter` must not be used after being freed.
259
+ #[unsafe(no_mangle)]
260
+ pub unsafe extern "C" fn rdx_graph_declarations_iter_free(iter: *mut DeclarationsIter) {
261
+ if iter.is_null() {
262
+ return;
263
+ }
264
+
265
+ unsafe {
266
+ let _ = Box::from_raw(iter);
267
+ }
268
+ }
269
+
270
+ /// An iterator over document (URI) IDs
271
+ ///
272
+ /// We snapshot the IDs at iterator creation so if the graph is modified, the iterator will not see the changes
273
+ #[derive(Debug)]
274
+ pub struct DocumentsIter {
275
+ /// The snapshot of document (URI) IDs
276
+ ids: Box<[i64]>,
277
+ /// The current index of the iterator
278
+ index: usize,
279
+ }
280
+
281
+ /// Creates a new iterator over document (URI) IDs by snapshotting the current set of IDs.
282
+ ///
283
+ /// # Safety
284
+ ///
285
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
286
+ /// - The returned pointer must be freed with `rdx_graph_documents_iter_free`.
287
+ #[unsafe(no_mangle)]
288
+ pub unsafe extern "C" fn rdx_graph_documents_iter_new(pointer: GraphPointer) -> *mut DocumentsIter {
289
+ // Snapshot the IDs at iterator creation to avoid borrowing across FFI calls
290
+ let ids = with_graph(pointer, |graph| {
291
+ graph
292
+ .documents()
293
+ .keys()
294
+ .map(|uri_id| **uri_id)
295
+ .collect::<Vec<i64>>()
296
+ .into_boxed_slice()
297
+ });
298
+
299
+ Box::into_raw(Box::new(DocumentsIter { ids, index: 0 }))
300
+ }
301
+
302
+ /// Returns the total number of IDs in the iterator snapshot.
303
+ ///
304
+ /// # Safety
305
+ ///
306
+ /// - `iter` must be a valid pointer previously returned by `rdx_graph_documents_iter_new`.
307
+ #[unsafe(no_mangle)]
308
+ pub unsafe extern "C" fn rdx_graph_documents_iter_len(iter: *const DocumentsIter) -> usize {
309
+ if iter.is_null() {
310
+ return 0;
311
+ }
312
+
313
+ unsafe { (&*iter).ids.len() }
314
+ }
315
+
316
+ /// Advances the iterator and writes the next ID into `out_id`.
317
+ /// Returns `true` if an ID was written, or `false` if the iterator is exhausted or inputs are invalid.
318
+ ///
319
+ /// # Safety
320
+ ///
321
+ /// - `iter` must be a valid pointer previously returned by `rdx_graph_documents_iter_new`.
322
+ /// - `out_id` must be a valid, writable pointer.
323
+ #[unsafe(no_mangle)]
324
+ pub unsafe extern "C" fn rdx_graph_documents_iter_next(iter: *mut DocumentsIter, out_id: *mut i64) -> bool {
325
+ if iter.is_null() || out_id.is_null() {
326
+ return false;
327
+ }
328
+
329
+ let it = unsafe { &mut *iter };
330
+ if it.index >= it.ids.len() {
331
+ return false;
332
+ }
333
+
334
+ let id = it.ids[it.index];
335
+ it.index += 1;
336
+ unsafe { *out_id = id };
337
+
338
+ true
339
+ }
340
+
341
+ /// Frees an iterator created by `rdx_graph_documents_iter_new`.
342
+ ///
343
+ /// # Safety
344
+ ///
345
+ /// - `iter` must be a pointer previously returned by `rdx_graph_documents_iter_new`.
346
+ /// - `iter` must not be used after being freed.
347
+ #[unsafe(no_mangle)]
348
+ pub unsafe extern "C" fn rdx_graph_documents_iter_free(iter: *mut DocumentsIter) {
349
+ if iter.is_null() {
350
+ return;
351
+ }
352
+
353
+ unsafe {
354
+ let _ = Box::from_raw(iter);
355
+ }
356
+ }
357
+
358
+ /// Attempts to resolve a declaration from a fully-qualified name string.
359
+ /// Returns a pointer to the internal ID if it exists, or NULL if it does not.
360
+ ///
361
+ /// # Safety
362
+ /// - `pointer` must be a valid `GraphPointer`
363
+ /// - `name` must be a valid, null-terminated UTF-8 string
364
+ /// - `out_id` must be a valid, writable pointer
365
+ ///
366
+ /// # Panics
367
+ ///
368
+ /// Will panic if acquiring a read lock on the graph's declarations fails
369
+ #[unsafe(no_mangle)]
370
+ pub unsafe extern "C" fn rdx_graph_get_declaration(pointer: GraphPointer, name: *const c_char) -> *const i64 {
371
+ let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
372
+ return ptr::null();
373
+ };
374
+
375
+ with_graph(pointer, |graph| {
376
+ // TODO: We should perform name resolution instead of accessing the graph with the canonical ID
377
+ let decl_id = DeclarationId::from(name_str.as_str());
378
+ if graph.declarations().contains_key(&decl_id) {
379
+ Box::into_raw(Box::new(*decl_id)).cast_const()
380
+ } else {
381
+ ptr::null()
382
+ }
383
+ })
384
+ }
385
+
386
+ /// Creates a new iterator over constant references by snapshotting the current set of (id, kind) pairs.
387
+ ///
388
+ /// # Safety
389
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
390
+ #[unsafe(no_mangle)]
391
+ pub unsafe extern "C" fn rdx_graph_constant_references_iter_new(pointer: GraphPointer) -> *mut ReferencesIter {
392
+ with_graph(pointer, |graph| {
393
+ let refs: Vec<(i64, ReferenceKind)> = graph
394
+ .constant_references()
395
+ .keys()
396
+ .map(|id| (**id, ReferenceKind::Constant))
397
+ .collect();
398
+
399
+ ReferencesIter::new(refs.into_boxed_slice())
400
+ })
401
+ }
402
+
403
+ /// Creates a new iterator over method references by snapshotting the current set of (id, kind) pairs.
404
+ ///
405
+ /// # Safety
406
+ /// - `pointer` must be a valid `GraphPointer` previously returned by this crate.
407
+ #[unsafe(no_mangle)]
408
+ pub unsafe extern "C" fn rdx_graph_method_references_iter_new(pointer: GraphPointer) -> *mut ReferencesIter {
409
+ with_graph(pointer, |graph| {
410
+ let refs: Vec<(i64, ReferenceKind)> = graph
411
+ .method_references()
412
+ .keys()
413
+ .map(|id| (**id, ReferenceKind::Method))
414
+ .collect();
415
+
416
+ ReferencesIter::new(refs.into_boxed_slice())
417
+ })
418
+ }
419
+
420
+ #[cfg(test)]
421
+ mod tests {
422
+ use rubydex::indexing::ruby_indexer::RubyIndexer;
423
+
424
+ use super::*;
425
+
426
+ #[test]
427
+ fn names_are_untracked_after_resolving_constant() {
428
+ let mut indexer = RubyIndexer::new(
429
+ "file:///foo.rb".into(),
430
+ "
431
+ class Foo
432
+ BAR = 1
433
+ end
434
+ ",
435
+ );
436
+ indexer.index();
437
+
438
+ let mut graph = Graph::new();
439
+ graph.update(indexer.local_graph());
440
+ let mut resolver = Resolver::new(&mut graph);
441
+ resolver.resolve_all();
442
+
443
+ assert_eq!(
444
+ 1,
445
+ graph
446
+ .names()
447
+ .iter()
448
+ .find_map(|(_, name)| {
449
+ if graph.strings().get(name.str()).unwrap().as_str() == "BAR" {
450
+ Some(name)
451
+ } else {
452
+ None
453
+ }
454
+ })
455
+ .unwrap()
456
+ .ref_count()
457
+ );
458
+
459
+ let graph_ptr = Box::into_raw(Box::new(graph)) as GraphPointer;
460
+
461
+ // Build the nesting array: ["Foo"] since BAR is inside class Foo
462
+ let nesting_strings: Vec<CString> = vec![CString::new("Foo").unwrap()];
463
+ let nesting_ptrs: Vec<*const c_char> = nesting_strings.iter().map(|s| s.as_ptr()).collect();
464
+
465
+ unsafe {
466
+ let id = rdx_graph_resolve_constant(
467
+ graph_ptr,
468
+ CString::new("BAR").unwrap().as_ptr(),
469
+ nesting_ptrs.as_ptr(),
470
+ nesting_ptrs.len(),
471
+ );
472
+ assert_eq!(*id, *DeclarationId::from("Foo::BAR"));
473
+ };
474
+
475
+ let graph = unsafe { Box::from_raw(graph_ptr.cast::<Graph>()) };
476
+
477
+ assert_eq!(
478
+ 1,
479
+ graph
480
+ .names()
481
+ .iter()
482
+ .find_map(|(_, name)| {
483
+ if graph.strings().get(name.str()).unwrap().as_str() == "BAR" {
484
+ Some(name)
485
+ } else {
486
+ None
487
+ }
488
+ })
489
+ .unwrap()
490
+ .ref_count()
491
+ );
492
+ }
493
+ }
@@ -0,0 +1,9 @@
1
+ pub mod declaration_api;
2
+ pub mod definition_api;
3
+ pub mod diagnostic_api;
4
+ pub mod document_api;
5
+ pub mod graph_api;
6
+ pub mod location_api;
7
+ pub mod name_api;
8
+ pub mod reference_api;
9
+ pub mod utils;
@@ -0,0 +1,79 @@
1
+ //! Location-related C API and structs
2
+
3
+ use libc::c_char;
4
+ use rubydex::model::document::Document;
5
+ use rubydex::model::graph::Graph;
6
+ use rubydex::offset::Offset;
7
+ use std::ffi::CString;
8
+
9
+ /// C-compatible struct representing a definition location with offsets and line/column positions.
10
+ #[repr(C)]
11
+ #[derive(Debug, Clone)]
12
+ pub struct Location {
13
+ pub uri: *const c_char,
14
+ pub start_line: u32,
15
+ pub end_line: u32,
16
+ pub start_column: u32,
17
+ pub end_column: u32,
18
+ }
19
+
20
+ /// Helper to create a location for a given URI and byte-offset range.
21
+ /// Allocates and returns a pointer to `Location`. Caller must free with `rdx_location_free`.
22
+ ///
23
+ /// # Panics
24
+ ///
25
+ /// - If the URI cannot be converted to a file path.
26
+ /// - If the file cannot be read.
27
+ /// - If the offset cannot be converted to a position.
28
+ #[must_use]
29
+ pub(crate) fn create_location_for_uri_and_offset(graph: &Graph, document: &Document, offset: &Offset) -> *mut Location {
30
+ let line_index = document.line_index();
31
+ let start_pos = line_index.line_col(offset.start().into());
32
+ let end_pos = line_index.line_col(offset.end().into());
33
+
34
+ let loc = if let Some(wide_encoding) = graph.encoding().to_wide() {
35
+ let wide_start_pos = line_index.to_wide(wide_encoding, start_pos).unwrap();
36
+ let wide_end_pos = line_index.to_wide(wide_encoding, end_pos).unwrap();
37
+
38
+ Location {
39
+ uri: CString::new(document.uri()).unwrap().into_raw().cast_const(),
40
+ start_line: wide_start_pos.line + 1,
41
+ end_line: wide_end_pos.line + 1,
42
+ start_column: wide_start_pos.col + 1,
43
+ end_column: wide_end_pos.col + 1,
44
+ }
45
+ } else {
46
+ Location {
47
+ uri: CString::new(document.uri()).unwrap().into_raw().cast_const(),
48
+ start_line: start_pos.line + 1,
49
+ end_line: end_pos.line + 1,
50
+ start_column: start_pos.col + 1,
51
+ end_column: end_pos.col + 1,
52
+ }
53
+ };
54
+
55
+ Box::into_raw(Box::new(loc))
56
+ }
57
+
58
+ /// Frees a `Location` struct and its owned inner strings.
59
+ ///
60
+ /// # Safety
61
+ ///
62
+ /// - `ptr` must be a valid pointer previously returned by `create_location_for_uri_and_offset`.
63
+ /// - `ptr` must not be used after being freed.
64
+ #[unsafe(no_mangle)]
65
+ pub unsafe extern "C" fn rdx_location_free(ptr: *mut Location) {
66
+ if ptr.is_null() {
67
+ return;
68
+ }
69
+ unsafe {
70
+ // Take ownership of the box so we can free inner allocations first
71
+ let boxed = Box::from_raw(ptr);
72
+
73
+ if !boxed.uri.is_null() {
74
+ let _ = CString::from_raw(boxed.uri.cast_mut());
75
+ }
76
+
77
+ // Box drops here, freeing the struct memory
78
+ }
79
+ }
@@ -0,0 +1,81 @@
1
+ use rubydex::model::{graph::Graph, ids::NameId, name::Name};
2
+
3
+ /// Takes a constant name and a nesting stack (e.g.: `["Foo", "Bar::Baz", "Qux"]`) and transforms it into a `NameId`,
4
+ /// registering each required part in the graph. Returns the `NameId` and a list of name ids that need to be untracked
5
+ /// afterwards
6
+ ///
7
+ /// # Panics
8
+ ///
9
+ /// Should not panic because `const_name` will always be turned into a name
10
+ pub fn nesting_stack_to_name_id(graph: &mut Graph, const_name: &str, nesting: Vec<String>) -> (NameId, Vec<NameId>) {
11
+ let mut current_nesting = None;
12
+ let mut current_name = None;
13
+ let mut names_to_untrack = Vec::new();
14
+
15
+ for entry in nesting {
16
+ for part in entry.split("::").map(String::from) {
17
+ let str_id = graph.intern_string(part);
18
+ let name_id = graph.add_name(Name::new(str_id, current_name, current_nesting));
19
+ names_to_untrack.push(name_id);
20
+ let new_name = Some(name_id);
21
+ current_name = new_name;
22
+ }
23
+
24
+ current_nesting = current_name;
25
+ current_name = None;
26
+ }
27
+
28
+ for part in const_name.split("::").map(String::from) {
29
+ let str_id = graph.intern_string(part);
30
+ let name_id = graph.add_name(Name::new(str_id, current_name, current_nesting));
31
+ names_to_untrack.push(name_id);
32
+ let new_name = Some(name_id);
33
+ current_name = new_name;
34
+ }
35
+
36
+ (
37
+ current_name.expect("The NameId cannot be None since it contains at least `const_name`"),
38
+ names_to_untrack,
39
+ )
40
+ }
41
+
42
+ #[cfg(test)]
43
+ mod tests {
44
+ use rubydex::model::ids::StringId;
45
+
46
+ use super::*;
47
+
48
+ #[test]
49
+ fn nesting_is_converted_to_name_id() {
50
+ let mut graph = Graph::new();
51
+
52
+ let (name_id, _) = nesting_stack_to_name_id(
53
+ &mut graph,
54
+ "Some::CONST",
55
+ vec!["Foo".into(), "Bar::Zip".into(), "Qux".into()],
56
+ );
57
+
58
+ let const_name = graph.names().get(&name_id).unwrap();
59
+ assert_eq!(StringId::from("CONST"), *const_name.str());
60
+
61
+ let some_name = graph.names().get(&const_name.parent_scope().unwrap()).unwrap();
62
+ assert_eq!(StringId::from("Some"), *some_name.str());
63
+ assert_eq!(const_name.nesting(), some_name.nesting());
64
+
65
+ let qux_name = graph.names().get(&some_name.nesting().unwrap()).unwrap();
66
+ assert_eq!(StringId::from("Qux"), *qux_name.str());
67
+ assert!(qux_name.parent_scope().is_none());
68
+
69
+ let zip_name = graph.names().get(&qux_name.nesting().unwrap()).unwrap();
70
+ assert_eq!(StringId::from("Zip"), *zip_name.str());
71
+
72
+ let bar_name = graph.names().get(&zip_name.parent_scope().unwrap()).unwrap();
73
+ assert_eq!(StringId::from("Bar"), *bar_name.str());
74
+ assert_eq!(zip_name.nesting(), bar_name.nesting());
75
+
76
+ let foo_name = graph.names().get(&bar_name.nesting().unwrap()).unwrap();
77
+ assert_eq!(StringId::from("Foo"), *foo_name.str());
78
+ assert!(foo_name.parent_scope().is_none());
79
+ assert!(foo_name.nesting().is_none());
80
+ }
81
+ }