3 * Copyright (c) 2001-2002, Biswapesh Chattopadhyay
5 * This source code is released for free distribution under the terms of the
6 * GNU General Public License.
12 #include <glib-object.h>
15 #include "tm_ctags_wrappers.h"
18 #define TAG_NEW(T) ((T) = g_slice_new0(TMTag))
19 #define TAG_FREE(T) g_slice_free(TMTag, (T))
24 static GHashTable
*alive_tags
= NULL
;
26 static void foreach_tags_log(gpointer key
, gpointer value
, gpointer data
)
28 gsize
*ref_count
= data
;
29 const TMTag
*tag
= value
;
31 *ref_count
+= (gsize
) tag
->refcount
;
32 g_debug("Leaked TMTag (%d refs): %s", tag
->refcount
, tag
->name
);
35 static void log_refs_at_exit(void)
39 g_hash_table_foreach(alive_tags
, foreach_tags_log
, &ref_count
);
40 g_debug("TMTag references left at exit: %lu", ref_count
);
43 static TMTag
*log_tag_new(void)
49 alive_tags
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
50 atexit(log_refs_at_exit
);
53 g_hash_table_insert(alive_tags
, tag
, tag
);
58 static void log_tag_free(TMTag
*tag
)
60 g_return_if_fail(alive_tags
!= NULL
);
62 if (! g_hash_table_remove(alive_tags
, tag
)) {
63 g_critical("Freeing invalid TMTag pointer %p", (void *) tag
);
71 #define TAG_NEW(T) ((T) = log_tag_new())
72 #define TAG_FREE(T) log_tag_free(T)
74 #endif /* DEBUG_TAG_REFS */
81 const GPtrArray
*tags_array
;
85 /* Gets the GType for a TMTag */
86 GType
tm_tag_get_type(void)
88 static GType gtype
= 0;
89 if (G_UNLIKELY (gtype
== 0))
91 gtype
= g_boxed_type_register_static("TMTag", (GBoxedCopyFunc
)tm_tag_ref
,
92 (GBoxedFreeFunc
)tm_tag_unref
);
98 Creates a new tag structure and returns a pointer to it.
99 @return the new TMTag structure. This should be free()-ed using tm_tag_free()
101 TMTag
*tm_tag_new(void)
112 Destroys a TMTag structure, i.e. frees all elements except the tag itself.
113 @param tag The TMTag structure to destroy
116 static void tm_tag_destroy(TMTag
*tag
)
119 g_free(tag
->arglist
);
121 g_free(tag
->inheritance
);
122 g_free(tag
->var_type
);
127 Drops a reference from a TMTag. If the reference count reaches 0, this function
128 destroys all data in the tag and frees the tag structure as well.
129 @param tag Pointer to a TMTag structure
131 void tm_tag_unref(TMTag
*tag
)
133 /* be NULL-proof because tm_tag_free() was NULL-proof and we indent to be a
134 * drop-in replacment of it */
135 if (NULL
!= tag
&& g_atomic_int_dec_and_test(&tag
->refcount
))
143 Adds a reference to a TMTag.
144 @param tag Pointer to a TMTag structure
145 @return the passed-in TMTag
147 TMTag
*tm_tag_ref(TMTag
*tag
)
149 g_atomic_int_inc(&tag
->refcount
);
154 Inbuilt tag comparison function.
156 static gint
tm_tag_compare(gconstpointer ptr1
, gconstpointer ptr2
, gpointer user_data
)
158 unsigned int *sort_attr
;
160 TMTag
*t1
= *((TMTag
**) ptr1
);
161 TMTag
*t2
= *((TMTag
**) ptr2
);
162 TMSortOptions
*sort_options
= user_data
;
164 if ((NULL
== t1
) || (NULL
== t2
))
166 g_warning("Found NULL tag");
169 if (NULL
== sort_options
->sort_attrs
)
171 if (sort_options
->partial
)
172 return strncmp(FALLBACK(t1
->name
, ""), FALLBACK(t2
->name
, ""), strlen(FALLBACK(t1
->name
, "")));
174 return strcmp(FALLBACK(t1
->name
, ""), FALLBACK(t2
->name
, ""));
177 for (sort_attr
= sort_options
->sort_attrs
; returnval
== 0 && *sort_attr
!= tm_tag_attr_none_t
; ++ sort_attr
)
181 case tm_tag_attr_name_t
:
182 if (sort_options
->partial
)
183 returnval
= strncmp(FALLBACK(t1
->name
, ""), FALLBACK(t2
->name
, ""), strlen(FALLBACK(t1
->name
, "")));
185 returnval
= strcmp(FALLBACK(t1
->name
, ""), FALLBACK(t2
->name
, ""));
187 case tm_tag_attr_file_t
:
188 returnval
= t1
->file
- t2
->file
;
190 case tm_tag_attr_line_t
:
191 returnval
= t1
->line
- t2
->line
;
193 case tm_tag_attr_type_t
:
194 returnval
= t1
->type
- t2
->type
;
196 case tm_tag_attr_scope_t
:
197 returnval
= strcmp(FALLBACK(t1
->scope
, ""), FALLBACK(t2
->scope
, ""));
199 case tm_tag_attr_arglist_t
:
200 returnval
= strcmp(FALLBACK(t1
->arglist
, ""), FALLBACK(t2
->arglist
, ""));
203 int line_diff
= (t1
->line
- t2
->line
);
205 returnval
= line_diff
? line_diff
: returnval
;
208 case tm_tag_attr_vartype_t
:
209 returnval
= strcmp(FALLBACK(t1
->var_type
, ""), FALLBACK(t2
->var_type
, ""));
216 gboolean
tm_tags_equal(const TMTag
*a
, const TMTag
*b
)
221 return (a
->line
== b
->line
&&
222 a
->file
== b
->file
/* ptr comparison */ &&
223 strcmp(FALLBACK(a
->name
, ""), FALLBACK(b
->name
, "")) == 0 &&
224 a
->type
== b
->type
&&
225 a
->local
== b
->local
&&
226 a
->pointerOrder
== b
->pointerOrder
&&
227 a
->access
== b
->access
&&
228 a
->impl
== b
->impl
&&
229 a
->lang
== b
->lang
&&
230 strcmp(FALLBACK(a
->scope
, ""), FALLBACK(b
->scope
, "")) == 0 &&
231 strcmp(FALLBACK(a
->arglist
, ""), FALLBACK(b
->arglist
, "")) == 0 &&
232 strcmp(FALLBACK(a
->inheritance
, ""), FALLBACK(b
->inheritance
, "")) == 0 &&
233 strcmp(FALLBACK(a
->var_type
, ""), FALLBACK(b
->var_type
, "")) == 0);
237 Removes NULL tag entries from an array of tags. Called after tm_tags_dedup() since
238 this function substitutes duplicate entries with NULL
239 @param tags_array Array of tags to dedup
241 void tm_tags_prune(GPtrArray
*tags_array
)
245 g_return_if_fail(tags_array
);
247 for (i
=0, count
= 0; i
< tags_array
->len
; ++i
)
249 if (NULL
!= tags_array
->pdata
[i
])
250 tags_array
->pdata
[count
++] = tags_array
->pdata
[i
];
252 tags_array
->len
= count
;
256 Deduplicates an array on tags using the inbuilt comparison function based on
257 the attributes specified. Called by tm_tags_sort() when dedup is TRUE.
258 @param tags_array Array of tags to dedup.
259 @param sort_attributes Attributes the array is sorted on. They will be deduped
260 on the same criteria.
262 void tm_tags_dedup(GPtrArray
*tags_array
, TMTagAttrType
*sort_attributes
, gboolean unref_duplicates
)
264 TMSortOptions sort_options
;
267 g_return_if_fail(tags_array
);
268 if (tags_array
->len
< 2)
271 sort_options
.sort_attrs
= sort_attributes
;
272 sort_options
.partial
= FALSE
;
273 for (i
= 1; i
< tags_array
->len
; ++i
)
275 if (0 == tm_tag_compare(&(tags_array
->pdata
[i
- 1]), &(tags_array
->pdata
[i
]), &sort_options
))
277 if (unref_duplicates
)
278 tm_tag_unref(tags_array
->pdata
[i
-1]);
279 tags_array
->pdata
[i
-1] = NULL
;
282 tm_tags_prune(tags_array
);
286 Sort an array of tags on the specified attribuites using the inbuilt comparison
288 @param tags_array The array of tags to be sorted
289 @param sort_attributes Attributes to be sorted on (int array terminated by 0)
290 @param dedup Whether to deduplicate the sorted array
292 void tm_tags_sort(GPtrArray
*tags_array
, TMTagAttrType
*sort_attributes
,
293 gboolean dedup
, gboolean unref_duplicates
)
295 TMSortOptions sort_options
;
297 g_return_if_fail(tags_array
);
299 sort_options
.sort_attrs
= sort_attributes
;
300 sort_options
.partial
= FALSE
;
301 g_ptr_array_sort_with_data(tags_array
, tm_tag_compare
, &sort_options
);
303 tm_tags_dedup(tags_array
, sort_attributes
, unref_duplicates
);
306 void tm_tags_remove_file_tags(TMSourceFile
*source_file
, GPtrArray
*tags_array
)
310 /* Now we choose between an algorithm with complexity O(tags_array->len) and
311 * O(source_file->tags_array->len * log(tags_array->len)). The latter algorithm
312 * is better when tags_array contains many times more tags than
313 * source_file->tags_array so instead of trying to find the removed tags
314 * linearly, binary search is used. The constant 20 is more or less random
315 * but seems to work well. It's exact value isn't so critical because it's
316 * the extremes where the difference is the biggest: when
317 * source_file->tags_array->len == tags_array->len (single file open) and
318 * source_file->tags_array->len << tags_array->len (the number of tags
319 * from the file is a small fraction of all tags).
321 if (source_file
->tags_array
->len
!= 0 &&
322 tags_array
->len
/ source_file
->tags_array
->len
< 20)
324 for (i
= 0; i
< tags_array
->len
; i
++)
326 TMTag
*tag
= tags_array
->pdata
[i
];
328 if (tag
->file
== source_file
)
329 tags_array
->pdata
[i
] = NULL
;
334 GPtrArray
*to_delete
= g_ptr_array_sized_new(source_file
->tags_array
->len
);
336 for (i
= 0; i
< source_file
->tags_array
->len
; i
++)
341 TMTag
*tag
= source_file
->tags_array
->pdata
[i
];
343 found
= tm_tags_find(tags_array
, tag
->name
, FALSE
, &tag_count
);
345 for (j
= 0; j
< tag_count
; j
++)
347 if (*found
!= NULL
&& (*found
)->file
== source_file
)
349 /* we cannot set the pointer to NULL now because the search wouldn't work */
350 g_ptr_array_add(to_delete
, found
);
351 /* no break - if there are multiple tags of the same name, we would
352 * always find the first instance and wouldn't remove others; duplicates
353 * in the to_delete list aren't a problem */
359 for (i
= 0; i
< to_delete
->len
; i
++)
361 TMTag
**tag
= to_delete
->pdata
[i
];
364 g_ptr_array_free(to_delete
, TRUE
);
367 tm_tags_prune(tags_array
);
370 /* Optimized merge sort for merging sorted values from one array to another
371 * where one of the arrays is much smaller than the other.
372 * The merge complexity depends mostly on the size of the small array
373 * and is almost independent of the size of the big array.
374 * In addition, get rid of the duplicates (if both big_array and small_array are duplicate-free). */
375 static GPtrArray
*merge(GPtrArray
*big_array
, GPtrArray
*small_array
,
376 TMSortOptions
*sort_options
, gboolean unref_duplicates
) {
377 guint i1
= 0; /* index to big_array */
378 guint i2
= 0; /* index to small_array */
381 GPtrArray
*res_array
= g_ptr_array_sized_new(big_array
->len
+ small_array
->len
);
386 /* swap the arrays if len(small) > len(big) */
387 if (small_array
->len
> big_array
->len
)
389 GPtrArray
*tmp
= small_array
;
390 small_array
= big_array
;
394 /* on average, we are merging a value from small_array every
395 * len(big_array) / len(small_array) values - good approximation for fast jump
397 initial_step
= (small_array
->len
> 0) ? big_array
->len
/ small_array
->len
: 1;
398 initial_step
= initial_step
> 4 ? initial_step
: 1;
401 while (i1
< big_array
->len
&& i2
< small_array
->len
)
404 gpointer val2
= small_array
->pdata
[i2
];
406 if (step
> 4) /* fast path start */
408 guint j1
= (i1
+ step
< big_array
->len
) ? i1
+ step
: big_array
->len
- 1;
410 val1
= big_array
->pdata
[j1
];
414 /* if the value in big_array after making the big step is still smaller
415 * than the value in small_array, we can copy all the values inbetween
416 * into the result without making expensive string comparisons */
417 if (tm_tag_compare(&val1
, &val2
, sort_options
) < 0)
421 val1
= big_array
->pdata
[i1
];
422 g_ptr_array_add(res_array
, val1
);
428 /* lower the step and try again */
431 } /* fast path end */
439 val1
= big_array
->pdata
[i1
];
440 cmpval
= tm_tag_compare(&val1
, &val2
, sort_options
);
443 g_ptr_array_add(res_array
, val1
);
448 g_ptr_array_add(res_array
, val2
);
450 /* value from small_array gets merged - reset the step size */
454 i1
++; /* remove the duplicate, keep just the newly merged value */
455 if (unref_duplicates
)
462 /* end of one of the arrays reached - copy the rest from the other array */
463 while (i1
< big_array
->len
)
464 g_ptr_array_add(res_array
, big_array
->pdata
[i1
++]);
465 while (i2
< small_array
->len
)
466 g_ptr_array_add(res_array
, small_array
->pdata
[i2
++]);
469 printf("cmpnums: %u\n", cmpnum
);
470 printf("total tags: %u\n", big_array
->len
);
471 printf("merged tags: %u\n\n", small_array
->len
);
477 GPtrArray
*tm_tags_merge(GPtrArray
*big_array
, GPtrArray
*small_array
,
478 TMTagAttrType
*sort_attributes
, gboolean unref_duplicates
)
480 GPtrArray
*res_array
;
481 TMSortOptions sort_options
;
483 sort_options
.sort_attrs
= sort_attributes
;
484 sort_options
.partial
= FALSE
;
485 res_array
= merge(big_array
, small_array
, &sort_options
, unref_duplicates
);
490 This function will extract the tags of the specified types from an array of tags.
491 The returned value is a GPtrArray which should be free-d with a call to
492 g_ptr_array_free(array, TRUE). However, do not free the tags themselves since they
494 @param tags_array The original array of tags
495 @param tag_types - The tag types to extract. Can be a bitmask. For example, passing
496 (tm_tag_typedef_t | tm_tag_struct_t) will extract all typedefs and structures from
498 @return an array of tags (NULL on failure)
500 GPtrArray
*tm_tags_extract(GPtrArray
*tags_array
, TMTagType tag_types
)
505 g_return_val_if_fail(tags_array
, NULL
);
507 new_tags
= g_ptr_array_new();
508 for (i
=0; i
< tags_array
->len
; ++i
)
510 if (NULL
!= tags_array
->pdata
[i
])
512 if (tag_types
& (((TMTag
*) tags_array
->pdata
[i
])->type
))
513 g_ptr_array_add(new_tags
, tags_array
->pdata
[i
]);
520 Completely frees an array of tags.
521 @param tags_array Array of tags to be freed.
522 @param free_array Whether the GptrArray is to be freed as well.
524 void tm_tags_array_free(GPtrArray
*tags_array
, gboolean free_all
)
529 for (i
= 0; i
< tags_array
->len
; ++i
)
530 tm_tag_unref(tags_array
->pdata
[i
]);
532 g_ptr_array_free(tags_array
, TRUE
);
534 g_ptr_array_set_size(tags_array
, 0);
538 /* copy/pasted bsearch() from libc extended with user_data for comparison function
539 * and using glib types */
540 static gpointer
binary_search(gpointer key
, gpointer base
, size_t nmemb
,
541 GCompareDataFunc compar
, gpointer user_data
)
552 p
= (gpointer
) (((const gchar
*) base
) + (idx
* sizeof(gpointer
)));
553 comparison
= (*compar
) (key
, p
, user_data
);
556 else if (comparison
> 0)
565 static gint
tag_search_cmp(gconstpointer ptr1
, gconstpointer ptr2
, gpointer user_data
)
567 gint res
= tm_tag_compare(ptr1
, ptr2
, user_data
);
571 TMSortOptions
*sort_options
= user_data
;
572 const GPtrArray
*tags_array
= sort_options
->tags_array
;
573 TMTag
**tag
= (TMTag
**) ptr2
;
575 /* if previous/next (depending on sort options) tag equal, we haven't
576 * found the first/last tag in a sequence of equal tags yet */
577 if (sort_options
->first
&& ptr2
!= &tags_array
->pdata
[0]) {
578 if (tm_tag_compare(ptr1
, tag
- 1, user_data
) == 0)
581 else if (!sort_options
->first
&& ptr2
!= &tags_array
->pdata
[tags_array
->len
-1])
583 if (tm_tag_compare(ptr1
, tag
+ 1, user_data
) == 0)
591 Returns a pointer to the position of the first matching tag in a (sorted) tags array.
592 The passed array of tags must be already sorted by name (searched with binary search).
593 @param tags_array Tag array (sorted on name)
594 @param name Name of the tag to locate.
595 @param partial If TRUE, matches the first part of the name instead of doing exact match.
596 @param tagCount Return location of the matched tags.
598 TMTag
**tm_tags_find(const GPtrArray
*tags_array
, const char *name
,
599 gboolean partial
, guint
*tagCount
)
602 TMSortOptions sort_options
;
605 if (!tags_array
|| !tags_array
->len
)
608 tag
= g_new0(TMTag
, 1);
609 tag
->name
= (char *) name
;
611 sort_options
.sort_attrs
= NULL
;
612 sort_options
.partial
= partial
;
613 sort_options
.tags_array
= tags_array
;
614 sort_options
.first
= TRUE
;
615 first
= (TMTag
**)binary_search(&tag
, tags_array
->pdata
, tags_array
->len
,
616 tag_search_cmp
, &sort_options
);
623 sort_options
.first
= FALSE
;
624 first_pos
= first
- (TMTag
**)tags_array
->pdata
;
625 /* search between the first element and end */
626 last
= (TMTag
**)binary_search(&tag
, first
, tags_array
->len
- first_pos
,
627 tag_search_cmp
, &sort_options
);
628 *tagCount
= last
- first
+ 1;
632 return (TMTag
**) first
;
635 /* Returns TMTag which "own" given line
636 @param line Current line in edited file.
637 @param file_tags A GPtrArray of edited file TMTag pointers.
638 @param tag_types the tag types to include in the match
639 @return TMTag pointers to owner tag. */
641 tm_get_current_tag (GPtrArray
* file_tags
, const gulong line
, const TMTagType tag_types
)
643 TMTag
*matching_tag
= NULL
;
644 if (file_tags
&& file_tags
->len
)
647 gulong matching_line
= 0;
649 for (i
= 0; (i
< file_tags
->len
); ++i
)
651 TMTag
*tag
= TM_TAG (file_tags
->pdata
[i
]);
652 if (tag
&& tag
->type
& tag_types
&&
653 tag
->line
<= line
&& tag
->line
> matching_line
)
656 matching_line
= tag
->line
;
663 const gchar
*tm_tag_context_separator(TMParserType lang
)
667 case TM_PARSER_C
: /* for C++ .h headers or C structs */
669 case TM_PARSER_GLSL
: /* for structs */
670 /*case GEANY_FILETYPES_RUBY:*/ /* not sure what to use atm*/
672 case TM_PARSER_POWERSHELL
:
674 case TM_PARSER_ZEPHIR
:
677 /* avoid confusion with other possible separators in group/section name */
682 /* no context separator */
683 case TM_PARSER_ASCIIDOC
:
684 case TM_PARSER_TXT2TAGS
:
692 gboolean
tm_tag_is_anon(const TMTag
*tag
)
697 if (tag
->lang
== TM_PARSER_C
|| tag
->lang
== TM_PARSER_CPP
)
698 return sscanf(tag
->name
, "anon_%*[a-z]_%u%c", &i
, &dummy
) == 1;
699 else if (tag
->lang
== TM_PARSER_FORTRAN
|| tag
->lang
== TM_PARSER_F77
)
700 return sscanf(tag
->name
, "Structure#%u%c", &i
, &dummy
) == 1 ||
701 sscanf(tag
->name
, "Interface#%u%c", &i
, &dummy
) == 1 ||
702 sscanf(tag
->name
, "Enum#%u%c", &i
, &dummy
) == 1;
707 gboolean
tm_tag_langs_compatible(TMParserType lang
, TMParserType other
)
709 if (lang
== TM_PARSER_NONE
|| other
== TM_PARSER_NONE
)
713 /* Accept CPP tags for C lang and vice versa */
714 else if (lang
== TM_PARSER_C
&& other
== TM_PARSER_CPP
)
716 else if (lang
== TM_PARSER_CPP
&& other
== TM_PARSER_C
)
723 #ifdef TM_DEBUG /* various debugging functions */
726 Returns the type of tag as a string
727 @param tag The tag whose type is required
729 const char *tm_tag_type_name(const TMTag
*tag
)
731 g_return_val_if_fail(tag
, NULL
);
733 return tm_ctags_get_kind_name(tm_parser_get_tag_kind(tag
->type
, tag
->lang
), tag
->lang
);
737 Returns the TMTagType given the name of the type. Reverse of tm_tag_type_name.
738 @param tag_name Name of the tag type
740 TMTagType
tm_tag_name_type(const char* tag_name
, TMParserType lang
)
742 g_return_val_if_fail(tag_name
, tm_tag_undef_t
);
744 return tm_parser_get_tag_type(tm_ctags_get_kind_from_name(tag_name
, lang
), lang
);
747 static const char *tm_tag_impl_name(TMTag
*tag
)
749 g_return_val_if_fail(tag
, NULL
);
750 if (TAG_IMPL_VIRTUAL
== tag
->impl
)
756 static const char *tm_tag_access_name(TMTag
*tag
)
758 g_return_val_if_fail(tag
, NULL
);
759 if (TAG_ACCESS_PUBLIC
== tag
->access
)
761 else if (TAG_ACCESS_PROTECTED
== tag
->access
)
763 else if (TAG_ACCESS_PRIVATE
== tag
->access
)
770 Prints information about a tag to the given file pointer.
771 @param tag The tag whose info is required.
772 @param fp The file pointer of the file to print the info to.
774 void tm_tag_print(TMTag
*tag
, FILE *fp
)
776 const char *laccess
, *impl
, *type
;
779 laccess
= tm_tag_access_name(tag
);
780 impl
= tm_tag_impl_name(tag
);
781 type
= tm_tag_type_name(tag
);
783 fprintf(fp
, "%s ", laccess
);
785 fprintf(fp
, "%s ", impl
);
787 fprintf(fp
, "%s ", type
);
789 fprintf(fp
, "%s ", tag
->var_type
);
791 fprintf(fp
, "%s::", tag
->scope
);
792 fprintf(fp
, "%s", tag
->name
);
794 fprintf(fp
, "%s", tag
->arglist
);
795 if (tag
->inheritance
)
796 fprintf(fp
, " : from %s", tag
->inheritance
);
797 if ((tag
->file
) && (tag
->line
> 0))
798 fprintf(fp
, "[%s:%ld]", tag
->file
->file_name
804 Prints info about all tags in the array to the given file pointer.
806 void tm_tags_array_print(GPtrArray
*tags
, FILE *fp
)
810 if (!(tags
&& (tags
->len
> 0) && fp
))
812 for (i
= 0; i
< tags
->len
; ++i
)
814 tag
= TM_TAG(tags
->pdata
[i
]);
815 tm_tag_print(tag
, fp
);
820 Returns the depth of tag scope (useful for finding tag hierarchy
822 gint
tm_tag_scope_depth(const TMTag
*t
)
824 const gchar
*context_sep
= tm_tag_context_separator(t
->lang
);
829 for (s
= t
->scope
, depth
= 0; s
; s
= strstr(s
, context_sep
))
837 #endif /* TM_DEBUG */