diff --git a/ChangeLog b/ChangeLog index 9b1425646..4bd7301bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2007-02-03 Soren Sandmann + + * glib/gsequence.[ch]: New files implementing GSequence, a list + implemented using a binary tree. + * glib/glib.h, glib/glib.symbols: Update for GSequence. + * docs/reference: Add documentation for GSequence + * tests: Add sequence-test.c, a thorough test of all of + the GSequence API. + 2007-01-30 Matthias Clasen * glib/glib.symbols: diff --git a/docs/reference/glib/glib-docs.sgml b/docs/reference/glib/glib-docs.sgml index e3e55bdc6..82b6302aa 100644 --- a/docs/reference/glib/glib-docs.sgml +++ b/docs/reference/glib/glib-docs.sgml @@ -36,6 +36,7 @@ + @@ -163,6 +164,7 @@ synchronize their operation. &glib-Doubly-Linked-Lists; &glib-Singly-Linked-Lists; &glib-Double-ended-Queues; + &glib-Sequences; &glib-Trash-Stacks; &glib-Hash-Tables; &glib-Strings; diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index cbbb8cda5..cd6d354a3 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1822,6 +1822,60 @@ g_queue_unlink g_queue_delete_link +
+Sequences +sequence + +GSequence +GSequenceIter +GSequenceIterCompareFunc + + +g_sequence_new +g_sequence_free +g_sequence_get_length +g_sequence_foreach +g_sequence_foreach_range +g_sequence_sort +g_sequence_sort_iter + + +g_sequence_get_begin_iter +g_sequence_get_end_iter +g_sequence_get_iter_at_pos +g_sequence_append +g_sequence_prepend +g_sequence_insert_before +g_sequence_move +g_sequence_swap +g_sequence_insert_sorted +g_sequence_insert_sorted_iter +g_sequence_sort_changed +g_sequence_sort_changed_iter +g_sequence_remove +g_sequence_remove_range +g_sequence_move_range +g_sequence_search +g_sequence_search_iter + + +g_sequence_get +g_sequence_set + + +g_sequence_iter_is_begin +g_sequence_iter_is_end +g_sequence_iter_next +g_sequence_iter_prev +g_sequence_iter_get_position +g_sequence_iter_move +g_sequence_iter_get_sequence + + +g_sequence_iter_compare +g_sequence_range_get_midpoint +
+
Trash Stacks trash_stack diff --git a/docs/reference/glib/tmpl/glib-unused.sgml b/docs/reference/glib/tmpl/glib-unused.sgml index de6993de5..b089e6686 100644 --- a/docs/reference/glib/tmpl/glib-unused.sgml +++ b/docs/reference/glib/tmpl/glib-unused.sgml @@ -203,15 +203,6 @@ error domains. - - -This macro is defined as 1 if the the compiler supports ELF visibility -attributes (currently only gcc). - - -Since: 2.6 - - diff --git a/docs/reference/glib/tmpl/keyfile.sgml b/docs/reference/glib/tmpl/keyfile.sgml index d63ead05a..a427cf090 100644 --- a/docs/reference/glib/tmpl/keyfile.sgml +++ b/docs/reference/glib/tmpl/keyfile.sgml @@ -504,8 +504,6 @@ Flags which influence the parsing. @key_file: @group_name: @key: -@list: -@length: @@ -517,8 +515,6 @@ Flags which influence the parsing. @group_name: @key: @locale: -@list: -@length: diff --git a/docs/reference/glib/tmpl/sequence.sgml b/docs/reference/glib/tmpl/sequence.sgml new file mode 100644 index 000000000..325ab7813 --- /dev/null +++ b/docs/reference/glib/tmpl/sequence.sgml @@ -0,0 +1,433 @@ + +Sequences + + +scalable lists + + + + +The #GSequence data structure has the API of a list, but is +implemented internally with a balanced binary tree. This means that it +is possible to maintain a sorted list of n elements in time O(n log +n). The data contained in each element can be either integer values, by +using of the +Type Conversion Macros, +or simply pointers to any type of data. + + + +A #GSequence is accessed through iterators, +represented by a #GSequenceIter. An iterator represents a position +between two elements of the sequence. For example, the +begin iterator represents the gap immediately +before the first element of the sequence, and the +end iterator represents the gap immediately +after the last element. In an empty sequence, the begin and end +iterators are the same. + + + +Some methods on #GSequence operate on ranges of items. For example +g_sequence_foreach_range() will call a user-specified function on each +element with the given range. The range is delimited by the gaps +represented by the passed-in iterators, so if you pass in the begin +and end iterators, the range in question is the entire sequence. + + + + +The function g_sequence_get() is used with an iterator to access the +element immediately following the gap that the iterator +represents. The iterator is said to point to +that element. + + + +Iterators are stable across most operations on a #GSequence. For +example an iterator pointing to some element of a sequence will +continue to point to that element even after the sequence is +sorted. Even moving an element to another sequence using for example +g_sequence_move_range() will not invalidate the iterators pointing to +it. The only operation that will invalidate an iterator is when the +element it points to is removed from any sequence. + + + + + + + + + + + + + +The GSequence struct is an opaque data type +representing a Sequence data type. + + + + + +The GSequenceIter struct is an opaque data +type representing an iterator pointing into a #GSequence. + + + + + +A #GSequenceIterCompareFunc is a function used to compare +iterators. It must return zero if the iterators compare equal, a +negative value if @a comes before @b, and a positive value if @b comes +before @a. + + +@a: a #GSequenceIter +@b: a #GSequenceIter +@data: user data +@Returns: zero if the iterators are equal, a negative value if @a +comes before @b, and a positive value if @b comes before @a. + + + + + + + +@data_destroy: +@Returns: + + + + + + + +@seq: + + + + + + + +@seq: +@Returns: + + + + + + + +@seq: +@func: +@user_data: + + + + + + + +@begin: +@end: +@func: +@user_data: + + + + + + + +@seq: +@cmp_func: +@cmp_data: + + + + + + + +@seq: +@cmp_func: +@cmp_data: + + + + + + + +@seq: +@Returns: + + + + + + + +@seq: +@Returns: + + + + + + + +@seq: +@pos: +@Returns: + + + + + + + +@seq: +@data: +@Returns: + + + + + + + +@seq: +@data: +@Returns: + + + + + + + +@iter: +@data: +@Returns: + + + + + + + +@src: +@dest: + + + + + + + +@a: +@b: + + + + + + + +@seq: +@data: +@cmp_func: +@cmp_data: +@Returns: + + + + + + + +@seq: +@data: +@iter_cmp: +@cmp_data: +@Returns: + + + + + + + +@iter: +@cmp_func: +@cmp_data: + + + + + + + +@iter: +@iter_cmp: +@cmp_data: + + + + + + + +@iter: + + + + + + + +@begin: +@end: + + + + + + + +@dest: +@begin: +@end: + + + + + + + +@seq: +@data: +@cmp_func: +@cmp_data: +@Returns: + + + + + + + +@seq: +@data: +@iter_cmp: +@cmp_data: +@Returns: + + + + + + + +@iter: +@Returns: + + + + + + + +@iter: +@data: + + + + + + + +@iter: +@Returns: + + + + + + + +@iter: +@Returns: + + + + + + + +@iter: +@Returns: + + + + + + + +@iter: +@Returns: + + + + + + + +@iter: +@Returns: + + + + + + + +@iter: +@delta: +@Returns: + + + + + + + +@iter: +@Returns: + + + + + + + +@a: +@b: +@Returns: + + + + + + + +@begin: +@end: +@Returns: + + diff --git a/docs/reference/gobject/tmpl/enumerations_flags.sgml b/docs/reference/gobject/tmpl/enumerations_flags.sgml index 2b72931f2..1293d2fd8 100644 --- a/docs/reference/gobject/tmpl/enumerations_flags.sgml +++ b/docs/reference/gobject/tmpl/enumerations_flags.sgml @@ -232,11 +232,13 @@ than to write one yourself using g_enum_register_static(). @name: A nul-terminated string used as the name of the new type. +@_static_values: +@Returns: The new type identifier. + @const_static_values: An array of #GEnumValue structs for the possible enumeration values. The array is terminated by a struct with all members being 0. GObject keeps a reference to the data, so it cannot be stack-allocated. -@Returns: The new type identifier. @@ -250,10 +252,12 @@ than to write one yourself using g_flags_register_static(). @name: A nul-terminated string used as the name of the new type. +@_static_values: +@Returns: The new type identifier. + @const_static_values: An array of #GFlagsValue structs for the possible flags values. The array is terminated by a struct with all members being 0. GObject keeps a reference to the data, so it cannot be stack-allocated. -@Returns: The new type identifier. @@ -282,6 +286,8 @@ my_enum_complete_type_info (GTypePlugin *plugin, @g_enum_type: the type identifier of the type being completed @info: the #GTypeInfo struct to be filled in +@_values: + @const_values: An array of #GEnumValue structs for the possible enumeration values. The array is terminated by a struct with all members being 0. @@ -296,6 +302,8 @@ g_enumeration_complete_type_info() above. @g_flags_type: the type identifier of the type being completed @info: the #GTypeInfo struct to be filled in +@_values: + @const_values: An array of #GFlagsValue structs for the possible enumeration values. The array is terminated by a struct with all members being 0. diff --git a/docs/reference/gobject/tmpl/gparamspec.sgml b/docs/reference/gobject/tmpl/gparamspec.sgml index eb90ffda1..ceb5803f9 100644 --- a/docs/reference/gobject/tmpl/gparamspec.sgml +++ b/docs/reference/gobject/tmpl/gparamspec.sgml @@ -149,10 +149,6 @@ can be configured. parameter is guaranteed to remain valid and unmodified for the lifetime of the parameter. Since 2.8 -@G_PARAM_STATIC_NICK: the string used as nick when constructing the - parameter is guaranteed to remain valid and - unmodified for the lifetime of the parameter. - Since 2.8 @G_PARAM_STATIC_BLURB: the string used as blurb when constructing the parameter is guaranteed to remain valid and unmodified for the lifetime of the parameter. diff --git a/docs/reference/gobject/tmpl/gtypemodule.sgml b/docs/reference/gobject/tmpl/gtypemodule.sgml index 71ce8f1a0..3b2e9ee75 100644 --- a/docs/reference/gobject/tmpl/gtypemodule.sgml +++ b/docs/reference/gobject/tmpl/gtypemodule.sgml @@ -164,11 +164,13 @@ not be unloaded. @module: a #GTypeModule @name: name for the type +@_static_values: +@Returns: the new or existing type ID +@Since: 2.6 + @const_static_values: an array of #GEnumValue structs for the possible enumeration values. The array is terminated by a struct with all members being 0. -@Returns: the new or existing type ID -@Since: 2.6 @@ -185,11 +187,13 @@ not be unloaded. @module: a #GTypeModule @name: name for the type +@_static_values: +@Returns: the new or existing type ID +@Since: 2.6 + @const_static_values: an array of #GFlagsValue structs for the possible flags values. The array is terminated by a struct with all members being 0. -@Returns: the new or existing type ID -@Since: 2.6 diff --git a/glib/Makefile.am b/glib/Makefile.am index 1ea7a0f8e..4d3b2ad78 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -108,6 +108,7 @@ libglib_2_0_la_SOURCES = \ grand.c \ gscanner.c \ gscripttable.h \ + gsequence.c \ gshell.c \ gslice.c \ gslist.c \ @@ -186,6 +187,7 @@ glibsubinclude_HEADERS = \ grand.h \ grel.h \ gscanner.h \ + gsequence.h \ gshell.h \ gslice.h \ gslist.h \ diff --git a/glib/glib.h b/glib/glib.h index e5044f982..f3395aada 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/glib.symbols b/glib/glib.symbols index ef1adc6e5..034021565 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -924,6 +924,46 @@ g_scanner_warn G_GNUC_PRINTF(2,3) #endif #endif +#if IN_HEADER(__G_SEQUENCE_H__) +#if IN_FILE(__G_SEQUENCE_C__) +g_sequence_new +g_sequence_free +g_sequence_get_length +g_sequence_foreach +g_sequence_foreach_range +g_sequence_sort +g_sequence_sort_iter +g_sequence_get_begin_iter +g_sequence_get_end_iter +g_sequence_get_iter_at_pos +g_sequence_append +g_sequence_prepend +g_sequence_insert_before +g_sequence_move +g_sequence_swap +g_sequence_insert_sorted +g_sequence_insert_sorted_iter +g_sequence_sort_changed +g_sequence_sort_changed_iter +g_sequence_remove +g_sequence_remove_range +g_sequence_move_range +g_sequence_search +g_sequence_search_iter +g_sequence_get +g_sequence_set +g_sequence_iter_is_begin +g_sequence_iter_is_end +g_sequence_iter_next +g_sequence_iter_prev +g_sequence_iter_get_position +g_sequence_iter_move +g_sequence_iter_get_sequence +g_sequence_iter_compare +g_sequence_range_get_midpoint +#endif +#endif + #if IN_HEADER(__G_SHELL_H__) #if IN_FILE(__G_SHELL_C__) g_shell_error_quark diff --git a/glib/gsequence.c b/glib/gsequence.c new file mode 100644 index 000000000..5ecd46405 --- /dev/null +++ b/glib/gsequence.c @@ -0,0 +1,1747 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 + * Soeren Sandmann (sandmann@daimi.au.dk) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "glib.h" +#include "galias.h" + +typedef struct _GSequenceNode GSequenceNode; + +struct _GSequence +{ + GSequenceNode * end_node; + GDestroyNotify data_destroy_notify; + gboolean access_prohibited; +}; + +struct _GSequenceNode +{ + gint n_nodes; + GSequenceNode * parent; + GSequenceNode * left; + GSequenceNode * right; + gpointer data; /* For the end node, this field points + * to the sequence + */ +}; + +/* + * Declaration of GSequenceNode methods + */ +static GSequenceNode *node_new (gpointer data); +static GSequenceNode *node_get_first (GSequenceNode *node); +static GSequenceNode *node_get_last (GSequenceNode *node); +static GSequenceNode *node_get_prev (GSequenceNode *node); +static GSequenceNode *node_get_next (GSequenceNode *node); +static gint node_get_pos (GSequenceNode *node); +static GSequenceNode *node_get_by_pos (GSequenceNode *node, + gint pos); +static GSequenceNode *node_find_closest (GSequenceNode *haystack, + GSequenceNode *needle, + GSequenceNode *end, + GSequenceIterCompareFunc cmp, + gpointer user_data); +static gint node_get_length (GSequenceNode *node); +static void node_free (GSequenceNode *node, + GSequence *seq); +static void node_cut (GSequenceNode *split); +static void node_insert_after (GSequenceNode *node, + GSequenceNode *second); +static void node_insert_before (GSequenceNode *node, + GSequenceNode *new); +static void node_unlink (GSequenceNode *node); +static void node_insert_sorted (GSequenceNode *node, + GSequenceNode *new, + GSequenceNode *end, + GSequenceIterCompareFunc cmp_func, + gpointer cmp_data); + +/* + * Various helper functions + */ +static void +check_seq_access (GSequence *seq) +{ + if (G_UNLIKELY (seq->access_prohibited)) + { + g_warning ("Accessing a sequence while it is " + "being sorted or searched is not allowed"); + } +} + +static GSequence * +get_sequence (GSequenceNode *node) +{ + return (GSequence *)node_get_last (node)->data; +} + +static void +check_iter_access (GSequenceIter *iter) +{ + check_seq_access (get_sequence (iter)); +} + +static gboolean +is_end (GSequenceIter *iter) +{ + GSequence *seq = get_sequence (iter); + + return seq->end_node == iter; +} + +typedef struct +{ + GCompareDataFunc cmp_func; + gpointer cmp_data; + GSequenceNode *end_node; +} SortInfo; + +/* This function compares two iters using a normal compare + * function and user_data passed in in a SortInfo struct + */ +static gint +iter_compare (GSequenceIter *node1, + GSequenceIter *node2, + gpointer data) +{ + const SortInfo *info = data; + gint retval; + + if (node1 == info->end_node) + return 1; + + if (node2 == info->end_node) + return -1; + + retval = info->cmp_func (node1->data, node2->data, info->cmp_data); + + return retval; +} + +/* + * Public API + */ + +/** + * g_sequence_new: + * @data_destroy: a #GDestroyNotify function, or %NULL + * + * Creates a new GSequence. The @data_destroy function, if non-%NULL will + * be called on all items when the sequence is destroyed and on items that + * are removed from the sequence. + * + * Return value: a new #GSequence + * + * Since: 2.14 + **/ +GSequence * +g_sequence_new (GDestroyNotify data_destroy) +{ + GSequence *seq = g_new (GSequence, 1); + seq->data_destroy_notify = data_destroy; + + seq->end_node = node_new (seq); + + seq->access_prohibited = FALSE; + + return seq; +} + +/** + * g_sequence_free: + * @seq: a #GSequence + * + * Frees the memory allocated for @seq. If @seq has a data destroy + * function associated with it, that function is called on all items in + * @seq. + * + * Since: 2.14 + **/ +void +g_sequence_free (GSequence *seq) +{ + g_return_if_fail (seq != NULL); + + check_seq_access (seq); + + node_free (seq->end_node, seq); + + g_free (seq); +} + +/** + * g_sequence_foreach_range: + * @begin: a #GSequenceIter + * @end: a #GSequenceIter + * @func: a #GFunc + * @user_data: user data passed to @func + * + * Calls @func for each item in the range (@begin, @end) passing + * @user_data to the function. + * + * Since: 2.14 + **/ +void +g_sequence_foreach_range (GSequenceIter *begin, + GSequenceIter *end, + GFunc func, + gpointer user_data) +{ + GSequence *seq; + GSequenceIter *iter; + + g_return_if_fail (func != NULL); + g_return_if_fail (begin != NULL); + g_return_if_fail (end != NULL); + + seq = get_sequence (begin); + + seq->access_prohibited = TRUE; + + iter = begin; + while (iter != end) + { + GSequenceIter *next = node_get_next (iter); + + func (iter->data, user_data); + + iter = next; + } + + seq->access_prohibited = FALSE; +} + +/** + * g_sequence_foreach: + * @seq: a #GSequence + * @func: the function to call for each item in @seq + * @user_data: user data passed to @func + * + * Calls @func for each item in the sequence passing @user_data + * to the function. + * + * Since: 2.14 + **/ +void +g_sequence_foreach (GSequence *seq, + GFunc func, + gpointer user_data) +{ + GSequenceIter *begin, *end; + + check_seq_access (seq); + + begin = g_sequence_get_begin_iter (seq); + end = g_sequence_get_end_iter (seq); + + g_sequence_foreach_range (begin, end, func, user_data); +} + +/** + * g_sequence_range_get_midpoint: + * @begin: a #GSequenceIter + * @end: a #GSequenceIter + * + * Finds an iterator somewhere in the range (@begin, @end). This + * iterator will be close to the middle of the range, but is not + * guaranteed to be exactly in the middle. + * + * The @begin and @end iterators must both point to the same sequence and + * @begin must come before or be equal to @end in the sequence. + * + * Return value: A #GSequenceIter pointing somewhere in the + * (@begin, @end) range. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_range_get_midpoint (GSequenceIter *begin, + GSequenceIter *end) +{ + int begin_pos, end_pos, mid_pos; + + g_return_val_if_fail (begin != NULL, NULL); + g_return_val_if_fail (end != NULL, NULL); + g_return_val_if_fail (get_sequence (begin) == get_sequence (end), NULL); + + begin_pos = node_get_pos (begin); + end_pos = node_get_pos (end); + + g_return_val_if_fail (end_pos >= begin_pos, NULL); + + mid_pos = begin_pos + (end_pos - begin_pos) / 2; + + return node_get_by_pos (begin, mid_pos); +} + +/** + * g_sequence_iter_compare: + * @a: a #GSequenceIter + * @b: a #GSequenceIter + * + * Returns a negative number if @a comes before @b, 0 if they are equal, + * and a positive number if @a comes after @b. + * + * The @a and @b iterators must point into the same sequence. + * + * Return value: A negative number if @a comes before @b, 0 if they are + * equal, and a positive number if @a comes after @b. + * + * Since: 2.14 + **/ +gint +g_sequence_iter_compare (GSequenceIter *a, + GSequenceIter *b) +{ + gint a_pos, b_pos; + + g_return_val_if_fail (a != NULL, 0); + g_return_val_if_fail (b != NULL, 0); + g_return_val_if_fail (get_sequence (a) == get_sequence (b), 0); + + check_iter_access (a); + check_iter_access (b); + + a_pos = node_get_pos (a); + b_pos = node_get_pos (b); + + if (a_pos == b_pos) + return 0; + else if (a_pos > b_pos) + return 1; + else + return -1; +} + +/** + * g_sequence_append: + * @seq: a #GSequencePointer + * @data: the data for the new item + * + * Adds a new item to the end of @seq. + * + * Return value: an iterator pointing to the new item + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_append (GSequence *seq, + gpointer data) +{ + GSequenceNode *node; + + g_return_val_if_fail (seq != NULL, NULL); + + check_seq_access (seq); + + node = node_new (data); + node_insert_before (seq->end_node, node); + + return node; +} + +/** + * g_sequence_prepend: + * @seq: a #GSequence + * @data: the data for the new item + * + * Adds a new item to the front of @seq + * + * Return value: an iterator pointing to the new item + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_prepend (GSequence *seq, + gpointer data) +{ + GSequenceNode *node, *first; + + g_return_val_if_fail (seq != NULL, NULL); + + check_seq_access (seq); + + node = node_new (data); + first = node_get_first (seq->end_node); + + node_insert_before (first, node); + + return node; +} + +/** + * g_sequence_insert_before: + * @iter: a #GSequenceIter + * @data: the data for the new item + * + * Inserts a new item just before the item pointed to by @iter. + * + * Return value: an iterator pointing to the new item + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_insert_before (GSequenceIter *iter, + gpointer data) +{ + GSequenceNode *node; + + g_return_val_if_fail (iter != NULL, NULL); + + check_iter_access (iter); + + node = node_new (data); + + node_insert_before (iter, node); + + return node; +} + +/** + * g_sequence_remove: + * @iter: a #GSequenceIter + * + * Removes the item pointed to by @iter. It is an error to pass the + * end iterator to this function. + * + * If the sequnce has a data destroy function associated with it, this + * function is called on the data for the removed item. + * + * Since: 2.14 + **/ +void +g_sequence_remove (GSequenceIter *iter) +{ + GSequence *seq; + + g_return_if_fail (iter != NULL); + g_return_if_fail (!is_end (iter)); + + check_iter_access (iter); + + seq = get_sequence (iter); + + node_unlink (iter); + node_free (iter, seq); +} + +/** + * g_sequence_remove_range: + * @begin: a #GSequenceIter + * @end: a #GSequenceIter + * + * Removes all items in the (@begin, @end) range. + * + * If the sequence has a data destroy function associated with it, this + * function is called on the data for the removed items. + * + * Since: 2.14 + **/ +void +g_sequence_remove_range (GSequenceIter *begin, + GSequenceIter *end) +{ + g_return_if_fail (get_sequence (begin) == get_sequence (end)); + + check_iter_access (begin); + check_iter_access (end); + + g_sequence_move_range (NULL, begin, end); +} + +/** + * g_sequence_move_range: + * @dest: a #GSequenceIter + * @begin: a #GSequenceIter + * @end: a #GSequenceIter + * + * Inserts the (@begin, @end) range at the destination pointed to by ptr. + * The @begin and @end iters must point into the same sequence. It is + * allowed for @dest to point to a different sequence than the one pointed + * into by @begin and @end. + * + * If @dest is NULL, the range indicated by @begin and @end is + * removed from the sequence. If @dest iter points to a place within + * the (@begin, @end) range, the range does not move. + * + * Since: 2.14 + **/ +void +g_sequence_move_range (GSequenceIter *dest, + GSequenceIter *begin, + GSequenceIter *end) +{ + GSequence *src_seq; + GSequenceNode *first; + + g_return_if_fail (begin != NULL); + g_return_if_fail (end != NULL); + + check_iter_access (begin); + check_iter_access (end); + if (dest) + check_iter_access (dest); + + src_seq = get_sequence (begin); + + g_return_if_fail (src_seq == get_sequence (end)); + + /* Dest points to begin or end? */ + if (dest == begin || dest == end) + return; + + /* begin comes after end? */ + if (g_sequence_iter_compare (begin, end) >= 0) + return; + + /* dest points somewhere in the (begin, end) range? */ + if (dest && get_sequence (dest) == src_seq && + g_sequence_iter_compare (dest, begin) > 0 && + g_sequence_iter_compare (dest, end) < 0) + { + return; + } + + src_seq = get_sequence (begin); + + first = node_get_first (begin); + + node_cut (begin); + + node_cut (end); + + if (first != begin) + node_insert_after (node_get_last (first), end); + + if (dest) + node_insert_before (dest, begin); + else + node_free (begin, src_seq); +} + +/** + * g_sequence_sort: + * @seq: a #GSequence + * @cmp_func: the #GCompareDataFunc used to sort @seq. This function is + * passed two items of @seq and should return 0 if they are equal, + * a negative value fi the first comes before the second, and a + * positive value if the second comes before the first. + * @cmp_data: user data passed to @cmp_func + * + * Sorts @seq using @cmp_func. + * + * Since: 2.14 + **/ +void +g_sequence_sort (GSequence *seq, + GCompareDataFunc cmp_func, + gpointer cmp_data) +{ + SortInfo info = { cmp_func, cmp_data, seq->end_node }; + + check_seq_access (seq); + + g_sequence_sort_iter (seq, iter_compare, &info); +} + +/** + * g_sequence_insert_sorted: + * @seq: a #GSequence + * @data: the data to insert + * @cmp_func: the #GCompareDataFunc used to compare items in the sequence. It + * is called with two items of the @seq and @user_data. It should + * return 0 if the items are equal, a negative value if the first + * item comes before the second, and a positive value if the second + * item comes before the first. + * @cmp_data: user data passed to @cmp_func. + * + * Inserts @data into @sequence using @func to determine the new position. + * The sequence must already be sorted according to @cmp_func; otherwise the + * new position of @data is undefined. + * + * Return value: a #GSequenceIter pointing to the new item. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_insert_sorted (GSequence *seq, + gpointer data, + GCompareDataFunc cmp_func, + gpointer cmp_data) +{ + SortInfo info = { cmp_func, cmp_data, NULL }; + + g_return_val_if_fail (seq != NULL, NULL); + g_return_val_if_fail (cmp_func != NULL, NULL); + + info.end_node = seq->end_node; + check_seq_access (seq); + + return g_sequence_insert_sorted_iter (seq, data, iter_compare, &info); +} + +/** + * g_sequence_sort_changed: + * @iter: A #GSequenceIter + * @cmp_func: the #GCompareDataFunc used to compare items in the sequence. It + * is called with two items of the @seq and @user_data. It should + * return 0 if the items are equal, a negative value if the first + * item comes before the second, and a positive value if the second + * item comes before the first. + * @cmp_data: user data passed to @cmp_func. + * + * Moves the data pointed to a new position as indicated by @cmp_func. This + * function should be called for items in a sequence already sorted according + * to @cmp_func whenever some aspect of an item changes so that @cmp_func + * may return different values for that item. + * + * Since: 2.14 + **/ +void +g_sequence_sort_changed (GSequenceIter *iter, + GCompareDataFunc cmp_func, + gpointer cmp_data) +{ + SortInfo info = { cmp_func, cmp_data, NULL }; + + g_return_if_fail (!is_end (iter)); + + info.end_node = get_sequence (iter)->end_node; + check_iter_access (iter); + + g_sequence_sort_changed_iter (iter, iter_compare, &info); +} + +/** + * g_sequence_search: + * @seq: a #GSequence + * @data: data for the new item + * @cmp_func: the #GCompareDataFunc used to compare items in the sequence. It + * is called with two items of the @seq and @user_data. It should + * return 0 if the items are equal, a negative value if the first + * item comes before the second, and a positive value if the second + * item comes before the first. + * @cmp_data: user data passed to @cmp_func. + * + * Returns an iterator pointing to the position where @data would + * be inserted according to @cmp_func and @cmp_data. + * + * Return value: an #GSequenceIter pointing to the position where @data + * would have been inserted according to @cmp_func and @cmp_data. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_search (GSequence *seq, + gpointer data, + GCompareDataFunc cmp_func, + gpointer cmp_data) +{ + SortInfo info = { cmp_func, cmp_data, NULL }; + + g_return_val_if_fail (seq != NULL, NULL); + + info.end_node = seq->end_node; + check_seq_access (seq); + + return g_sequence_search_iter (seq, data, iter_compare, &info); +} + +/** + * g_sequence_sort_iter: + * @seq: a #GSequence + * @cmp_func: the #GSequenceItercompare used to compare iterators in the + * sequence. It is called with two iterators pointing into @seq. It should + * return 0 if the iterators are equal, a negative value if the first + * iterator comes before the second, and a positive value if the second + * iterator comes before the first. + * @cmp_data: user data passed to @cmp_func + * + * Like g_sequence_sort(), but uses a #GSequenceIterCompareFunc instead + * of a GCompareDataFunc as the compare function + * + * Since: 2.14 + **/ +void +g_sequence_sort_iter (GSequence *seq, + GSequenceIterCompareFunc cmp_func, + gpointer cmp_data) +{ + GSequence *tmp; + GSequenceNode *begin, *end; + + g_return_if_fail (seq != NULL); + g_return_if_fail (cmp_func != NULL); + + check_seq_access (seq); + + begin = g_sequence_get_begin_iter (seq); + end = g_sequence_get_end_iter (seq); + + tmp = g_sequence_new (NULL); + + g_sequence_move_range (g_sequence_get_begin_iter (tmp), begin, end); + + tmp->access_prohibited = TRUE; + seq->access_prohibited = TRUE; + + while (g_sequence_get_length (tmp) > 0) + { + GSequenceNode *node = g_sequence_get_begin_iter (tmp); + + node_insert_sorted (seq->end_node, node, seq->end_node, + cmp_func, cmp_data); + } + + tmp->access_prohibited = FALSE; + seq->access_prohibited = FALSE; + + g_sequence_free (tmp); +} + +/** + * g_sequence_sort_changed_iter: + * @iter: a #GSequenceIter + * @iter_cmp: the #GSequenceItercompare used to compare iterators in the + * sequence. It is called with two iterators pointing into @seq. It should + * return 0 if the iterators are equal, a negative value if the first + * iterator comes before the second, and a positive value if the second + * iterator comes before the first. + * @cmp_data: user data passed to @cmp_func + * + * Like g_sequence_sort_changed(), but uses + * a #GSequenceIterCompareFunc instead of a #GCompareDataFunc as + * the compare function. + * + * Since: 2.14 + **/ +void +g_sequence_sort_changed_iter (GSequenceIter *iter, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data) +{ + GSequence *seq, *tmp_seq; + GSequenceIter *next, *prev; + + g_return_if_fail (iter != NULL); + g_return_if_fail (!is_end (iter)); + g_return_if_fail (iter_cmp != NULL); + check_iter_access (iter); + + /* If one of the neighbours is equal to iter, then + * don't move it. This ensures that sort_changed() is + * a stable operation. + */ + + next = node_get_next (iter); + prev = node_get_prev (iter); + + if (prev != iter && iter_cmp (prev, iter, cmp_data) == 0) + return; + + if (!is_end (next) && iter_cmp (next, iter, cmp_data) == 0) + return; + + seq = get_sequence (iter); + + seq->access_prohibited = TRUE; + + tmp_seq = g_sequence_new (NULL); + node_unlink (iter); + node_insert_before (tmp_seq->end_node, iter); + + node_insert_sorted (seq->end_node, iter, seq->end_node, + iter_cmp, cmp_data); + + g_sequence_free (tmp_seq); + + seq->access_prohibited = FALSE; +} + +/** + * g_sequence_insert_sorted_iter: + * @seq: a #GSequence + * @data: data for the new item + * @iter_cmp: the #GSequenceItercompare used to compare iterators in the + * sequence. It is called with two iterators pointing into @seq. It should + * return 0 if the iterators are equal, a negative value if the first + * iterator comes before the second, and a positive value if the second + * iterator comes before the first. + * @cmp_data: user data passed to @cmp_func + * + * Like g_sequence_insert_sorted(), but uses + * a #GSequenceIterCompareFunc instead of a #GCompareDataFunc as + * the compare function. + * + * Return value: a #GSequenceIter pointing to the new item + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_insert_sorted_iter (GSequence *seq, + gpointer data, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data) +{ + GSequenceNode *new_node; + GSequence *tmp_seq; + + g_return_val_if_fail (seq != NULL, NULL); + g_return_val_if_fail (iter_cmp != NULL, NULL); + + check_seq_access (seq); + + seq->access_prohibited = TRUE; + + /* Create a new temporary sequence and put the new node into + * that. The reason for this is that the user compare function + * will be called with the new node, and if it dereferences, + * "is_end" will be called on it. But that will crash if the + * node is not actually in a sequence. + * + * node_insert_sorted() makes sure the node is unlinked before + * is is inserted. + * + * The reason we need the "iter" versions at all is that that + * is the only kind of compare functions GtkTreeView can use. + */ + tmp_seq = g_sequence_new (NULL); + new_node = g_sequence_append (tmp_seq, data); + + node_insert_sorted (seq->end_node, new_node, + seq->end_node, iter_cmp, cmp_data); + + g_sequence_free (tmp_seq); + + seq->access_prohibited = FALSE; + + return new_node; +} + +/** + * g_sequence_search_iter: + * @seq: a #GSequence + * @data: data for the new item + * @iter_cmp: the #GSequenceIterCompare function used to compare iterators + * in the sequence. It is called with two iterators pointing into @seq. + * It should return 0 if the iterators are equal, a negative value if the + * first iterator comes before the second, and a positive value if the + * second iterator comes before the first. + * @cmp_data: user data passed to @iter_cmp + * + * Like g_sequence_search(), but uses + * a #GSequenceIterCompareFunc instead of a #GCompareDataFunc as + * the compare function. + * + * Return value: a #GSequenceIter pointing to the position in @seq + * where @data would have been inserted according to @iter_cmp and @cmp_data. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_search_iter (GSequence *seq, + gpointer data, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data) +{ + GSequenceNode *node; + GSequenceNode *dummy; + GSequence *tmp_seq; + + g_return_val_if_fail (seq != NULL, NULL); + + check_seq_access (seq); + + seq->access_prohibited = TRUE; + + /* Create a new temporary sequence and put the dummy node into + * that. The reason for this is that the user compare function + * will be called with the new node, and if it dereferences, + * "is_end" will be called on it. But that will crash if the + * node is not actually in a sequence. + * + * node_insert_sorted() makes sure the node is unlinked before + * is is inserted. + * + * The reason we need the "iter" versions at all is that that + * is the only kind of compare functions GtkTreeView can use. + */ + tmp_seq = g_sequence_new (NULL); + dummy = g_sequence_append (tmp_seq, data); + + node = node_find_closest (seq->end_node, dummy, + seq->end_node, iter_cmp, cmp_data); + + g_sequence_free (tmp_seq); + + seq->access_prohibited = FALSE; + + return node; +} + +/** + * g_sequence_iter_get_sequence: + * @iter: a #GSequenceIter + * + * Returns the #GSequence that @iter points into. + * + * Return value: the #GSequence that @iter points into. + * + * Since: 2.14 + **/ +GSequence * +g_sequence_iter_get_sequence (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + + return get_sequence (iter); +} + +/** + * g_sequence_get: + * @iter: a #GSequenceIter + * + * Returns the data that @iter points to. + * + * Return value: the data that @iter points to + * + * Since: 2.14 + **/ +gpointer +g_sequence_get (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (!is_end (iter), NULL); + + return iter->data; +} + +/** + * g_sequence_set: + * @iter: a #GSequenceIter + * @data: new data for the item + * + * Changes the data for the item pointed to by @iter to be @data. If + * the sequence has a data destroy function associated with it, that + * function is called on the existing data that @iter pointed to. + * + * Since: 2.14 + **/ +void +g_sequence_set (GSequenceIter *iter, + gpointer data) +{ + GSequence *seq; + + g_return_if_fail (iter != NULL); + g_return_if_fail (!is_end (iter)); + + seq = get_sequence (iter); + + /* If @data is identical to iter->data, it is destroyed + * here. This will work right in case of ref-counted objects. Also + * it is similar to what ghashtables do. + * + * For non-refcounted data it's a little less convenient, but + * code relying on self-setting not destroying would be + * pretty dubious anyway ... + */ + + if (seq->data_destroy_notify) + seq->data_destroy_notify (iter->data); + + iter->data = data; +} + +/** + * g_sequence_get_length: + * @seq: a #GSequence + * + * Returns the length of @seq + * + * Return value: the length of @seq + * + * Since: 2.14 + **/ +gint +g_sequence_get_length (GSequence *seq) +{ + return node_get_length (seq->end_node) - 1; +} + +/** + * g_sequence_get_end_iter: + * @seq: a #GSequence + * + * Returns the end iterator for @seg + * + * Return value: the end iterator for @seq + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_get_end_iter (GSequence *seq) +{ + g_return_val_if_fail (seq != NULL, NULL); + + g_assert (is_end (seq->end_node)); + + return seq->end_node; +} + +/** + * g_sequence_get_begin_iter: + * @seq: a #GSequence + * + * Returns the begin iterator for @seq. + * + * Return value: the begin iterator for @seq. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_get_begin_iter (GSequence *seq) +{ + g_return_val_if_fail (seq != NULL, NULL); + return node_get_first (seq->end_node); +} + +static int +clamp_position (GSequence *seq, + int pos) +{ + gint len = g_sequence_get_length (seq); + + if (pos > len || pos < 0) + pos = len; + + return pos; +} + +/* + * if pos > number of items or -1, will return end pointer + */ +/** + * g_sequence_get_iter_at_pos: + * @seq: a #GSequence + * @pos: a position in @seq, or -1 for the end. + * + * Returns the iterator at position @pos. If @pos is negative or larger + * than the number of items in @seq, the end iterator is returned. + * + * Return value: The #GSequenceIter at position @pos + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_get_iter_at_pos (GSequence *seq, + gint pos) +{ + g_return_val_if_fail (seq != NULL, NULL); + + pos = clamp_position (seq, pos); + + return node_get_by_pos (seq->end_node, pos); +} + +/** + * g_sequence_move: + * @src: a #GSequenceIter pointing to the item to move + * @dest: a #GSequenceIter pointing to the position to which + * the item is moved. + * + * Moves the item pointed to by @src to the position indicated by @dest. + * After calling this function @dest will point to the position immediately + * after @src. + * + * Since: 2.14 + **/ +void +g_sequence_move (GSequenceIter *src, + GSequenceIter *dest) +{ + g_return_if_fail (src != NULL); + g_return_if_fail (dest != NULL); + g_return_if_fail (!is_end (src)); + + if (src == dest) + return; + + node_unlink (src); + node_insert_before (dest, src); +} + +/* GSequenceIter */ + +/** + * g_sequence_iter_is_end: + * @iter: a #GSequenceIter + * + * Returns whether @iter is the end iterator + * + * Return value: Whether @iter is the end iterator. + * + * Since: 2.14 + **/ +gboolean +g_sequence_iter_is_end (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, FALSE); + + return is_end (iter); +} + +/** + * g_sequence_iter_is_begin: + * @iter: a #GSequenceIter + * + * Returns whether @iter is the begin iterator + * + * Return value: whether @iter is the begin iterator + * + * Since: 2.14 + **/ +gboolean +g_sequence_iter_is_begin (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, FALSE); + + return (node_get_prev (iter) == iter); +} + +/** + * g_sequence_iter_get_position: + * @iter: a #GSequenceIter + * + * Returns the position of @iter + * + * Return value: the position of @iter + * + * Since: 2.14 + **/ +gint +g_sequence_iter_get_position (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, -1); + + return node_get_pos (iter); +} + +/** + * g_sequence_iter_next: + * @iter: a #GSequenceIter + * + * Returns an iterator pointing to the next position after @iter. If + * @iter is the end iterator, the end iterator is returned. + * + * Return value: a #GSequenceIter pointing to the next position after @iter. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_iter_next (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + + return node_get_next (iter); +} + +/** + * g_sequence_iter_prev: + * @iter: a #GSequenceIter + * + * Returns an iterator pointing to the previous position before @iter. If + * @iter is the begin iterator, the begin iterator is returned. + * + * Return value: a #GSequenceIter pointing to the previous position before + * @iter. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_iter_prev (GSequenceIter *iter) +{ + g_return_val_if_fail (iter != NULL, NULL); + + return node_get_prev (iter); +} + +/** + * g_sequence_iter_move: + * @iter: a #GSequenceIter + * @delta: A positive or negative number indicating how many positions away + * from @iter the returned #GSequenceIter will be. + * + * Returns the #GSequenceIter which is @delta positions away from @iter. + * If @iter is closer than -@delta positions to the beginning of the sequence, + * the begin iterator is returned. If @iter is closer than @delta positions + * to the end of the sequence, the end iterator is returned. + * + * Return value: a #GSequenceIter which is @delta positions away from @iter. + * + * Since: 2.14 + **/ +GSequenceIter * +g_sequence_iter_move (GSequenceIter *iter, + gint delta) +{ + gint new_pos; + + g_return_val_if_fail (iter != NULL, NULL); + + new_pos = node_get_pos (iter) + delta; + + new_pos = clamp_position (get_sequence (iter), new_pos); + + return node_get_by_pos (iter, new_pos); +} + +/** + * g_sequence_swap: + * @a: a #GSequenceIter + * @b: a #GSequenceIter + * + * Swaps the items pointed to by @a and @b + * + * Since: 2.14 + **/ +void +g_sequence_swap (GSequenceIter *a, + GSequenceIter *b) +{ + GSequenceNode *leftmost, *rightmost, *rightmost_next; + int a_pos, b_pos; + + g_return_if_fail (!g_sequence_iter_is_end (a)); + g_return_if_fail (!g_sequence_iter_is_end (b)); + + if (a == b) + return; + + a_pos = g_sequence_iter_get_position (a); + b_pos = g_sequence_iter_get_position (b); + + if (a_pos > b_pos) + { + leftmost = b; + rightmost = a; + } + else + { + leftmost = a; + rightmost = b; + } + + rightmost_next = node_get_next (rightmost); + + /* The situation is now like this: + * + * ..., leftmost, ......., rightmost, rightmost_next, ... + * + */ + g_sequence_move (rightmost, leftmost); + g_sequence_move (leftmost, rightmost_next); +} + +/* + * Implementation of the splay tree. + */ +static void +node_update_fields (GSequenceNode *node) +{ + g_assert (node != NULL); + + node->n_nodes = 1; + + if (node->left) + node->n_nodes += node->left->n_nodes; + + if (node->right) + node->n_nodes += node->right->n_nodes; +} + +#define NODE_LEFT_CHILD(n) (((n)->parent) && ((n)->parent->left) == (n)) +#define NODE_RIGHT_CHILD(n) (((n)->parent) && ((n)->parent->right) == (n)) + +static void +node_rotate (GSequenceNode *node) +{ + GSequenceNode *tmp, *old; + + g_assert (node->parent); + g_assert (node->parent != node); + + if (NODE_LEFT_CHILD (node)) + { + /* rotate right */ + tmp = node->right; + + node->right = node->parent; + node->parent = node->parent->parent; + if (node->parent) + { + if (node->parent->left == node->right) + node->parent->left = node; + else + node->parent->right = node; + } + + g_assert (node->right); + + node->right->parent = node; + node->right->left = tmp; + + if (node->right->left) + node->right->left->parent = node->right; + + old = node->right; + } + else + { + /* rotate left */ + tmp = node->left; + + node->left = node->parent; + node->parent = node->parent->parent; + if (node->parent) + { + if (node->parent->right == node->left) + node->parent->right = node; + else + node->parent->left = node; + } + + g_assert (node->left); + + node->left->parent = node; + node->left->right = tmp; + + if (node->left->right) + node->left->right->parent = node->left; + + old = node->left; + } + + node_update_fields (old); + node_update_fields (node); +} + +static GSequenceNode * +splay (GSequenceNode *node) +{ + while (node->parent) + { + if (!node->parent->parent) + { + /* zig */ + node_rotate (node); + } + else if ((NODE_LEFT_CHILD (node) && NODE_LEFT_CHILD (node->parent)) || + (NODE_RIGHT_CHILD (node) && NODE_RIGHT_CHILD (node->parent))) + { + /* zig-zig */ + node_rotate (node->parent); + node_rotate (node); + } + else + { + /* zig-zag */ + node_rotate (node); + node_rotate (node); + } + } + + return node; +} + +static GSequenceNode * +node_new (gpointer data) +{ + GSequenceNode *node = g_slice_new0 (GSequenceNode); + + node->parent = NULL; + node->parent = NULL; + node->left = NULL; + node->right = NULL; + + node->data = data; + node->n_nodes = 1; + + return node; +} + +static GSequenceNode * +find_min (GSequenceNode *node) +{ + splay (node); + + while (node->left) + node = node->left; + + return node; +} + +static GSequenceNode * +find_max (GSequenceNode *node) +{ + splay (node); + + while (node->right) + node = node->right; + + return node; +} + +static GSequenceNode * +node_get_first (GSequenceNode *node) +{ + return splay (find_min (node)); +} + +static GSequenceNode * +node_get_last (GSequenceNode *node) +{ + return splay (find_max (node)); +} + +static gint +get_n_nodes (GSequenceNode *node) +{ + if (node) + return node->n_nodes; + else + return 0; +} + +static GSequenceNode * +node_get_by_pos (GSequenceNode *node, + gint pos) +{ + gint i; + + g_assert (node != NULL); + + splay (node); + + while ((i = get_n_nodes (node->left)) != pos) + { + if (i < pos) + { + node = node->right; + pos -= (i + 1); + } + else + { + node = node->left; + g_assert (node->parent != NULL); + } + } + + return splay (node); +} + +static GSequenceNode * +node_get_prev (GSequenceNode *node) +{ + splay (node); + + if (node->left) + { + node = node->left; + while (node->right) + node = node->right; + } + + return splay (node); +} + +static GSequenceNode * +node_get_next (GSequenceNode *node) +{ + splay (node); + + if (node->right) + { + node = node->right; + while (node->left) + node = node->left; + } + + return splay (node); +} + +static gint +node_get_pos (GSequenceNode *node) +{ + splay (node); + + return get_n_nodes (node->left); +} + +/* Return closest node _strictly_ bigger than @needle. This node + * always exists because the tree has an explicit end node). + * This end node of @haystack must be passed in @end. + */ +static GSequenceNode * +node_find_closest (GSequenceNode *haystack, + GSequenceNode *needle, + GSequenceNode *end, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data) +{ + GSequenceNode *best; + gint c; + + g_assert (haystack); + + haystack = splay (haystack); + + do + { + best = haystack; + + /* iter_cmp can't be passed the end node, since the function may + * be user-supplied + */ + if (haystack == end) + c = 1; + else + c = iter_cmp (haystack, needle, cmp_data); + + /* In the following we don't break even if c == 0. Instaed we go on + * searching along the 'bigger' nodes, so that we find the last one + * that is equal to the needle. + */ + if (c > 0) + haystack = haystack->left; + else + haystack = haystack->right; + } + while (haystack != NULL); + + /* If the best node is smaller or equal to the data, then move one step + * to the right to make sure the best one is strictly bigger than the data + */ + if (best != end && c <= 0) + best = node_get_next (best); + + return best; +} + +static void +node_free (GSequenceNode *node, + GSequence *seq) +{ + GPtrArray *stack = g_ptr_array_new (); + + splay (node); + + g_ptr_array_add (stack, node); + + while (stack->len > 0) + { + node = g_ptr_array_remove_index (stack, stack->len - 1); + + if (node) + { + g_ptr_array_add (stack, node->right); + g_ptr_array_add (stack, node->left); + + if (seq && seq->data_destroy_notify && node != seq->end_node) + seq->data_destroy_notify (node->data); + + g_slice_free (GSequenceNode, node); + } + } + + g_ptr_array_free (stack, TRUE); +} + +/* Splits into two trees. @node will be part of the right tree + */ +static void +node_cut (GSequenceNode *node) +{ + splay (node); + + g_assert (node->parent == NULL); + + if (node->left) + node->left->parent = NULL; + + node->left = NULL; + node_update_fields (node); +} + +static void +node_insert_before (GSequenceNode *node, + GSequenceNode *new) +{ + g_assert (node != NULL); + g_assert (new != NULL); + + splay (node); + + new = splay (find_min (new)); + g_assert (new->left == NULL); + + if (node->left) + node->left->parent = new; + + new->left = node->left; + new->parent = node; + + node->left = new; + + node_update_fields (new); + node_update_fields (node); +} + +static void +node_insert_after (GSequenceNode *node, + GSequenceNode *new) +{ + g_assert (node != NULL); + g_assert (new != NULL); + + splay (node); + + new = splay (find_max (new)); + g_assert (new->right == NULL); + g_assert (node->parent == NULL); + + if (node->right) + node->right->parent = new; + + new->right = node->right; + new->parent = node; + + node->right = new; + + node_update_fields (new); + node_update_fields (node); +} + +static gint +node_get_length (GSequenceNode *node) +{ + g_assert (node != NULL); + + splay (node); + return node->n_nodes; +} + +static void +node_unlink (GSequenceNode *node) +{ + GSequenceNode *right, *left; + + splay (node); + + left = node->left; + right = node->right; + + node->parent = node->left = node->right = NULL; + node_update_fields (node); + + if (right) + { + right->parent = NULL; + + right = node_get_first (right); + g_assert (right->left == NULL); + + right->left = left; + if (left) + { + left->parent = right; + node_update_fields (right); + } + } + else if (left) + { + left->parent = NULL; + } +} + +static void +node_insert_sorted (GSequenceNode *node, + GSequenceNode *new, + GSequenceNode *end, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data) +{ + GSequenceNode *closest; + + closest = node_find_closest (node, new, end, iter_cmp, cmp_data); + + node_unlink (new); + + node_insert_before (closest, new); +} + +static gint +node_calc_height (GSequenceNode *node) +{ + gint left_height; + gint right_height; + + if (node) + { + left_height = 0; + right_height = 0; + + if (node->left) + left_height = node_calc_height (node->left); + + if (node->right) + right_height = node_calc_height (node->right); + + return MAX (left_height, right_height) + 1; + } + + return 0; +} + +/* Self-test function */ +static void +check_node (GSequenceNode *node) +{ + if (node) + { + g_assert (node->parent != node); + g_assert (node->n_nodes == + 1 + get_n_nodes (node->left) + get_n_nodes (node->right)); + check_node (node->left); + check_node (node->right); + } +} + +void +g_sequence_self_test_internal_to_glib_dont_use (GSequence *seq) +{ + GSequenceNode *node = splay (seq->end_node); + + check_node (node); +} + +#define __G_SEQUENCE_C__ +#include "galiasdef.c" diff --git a/glib/gsequence.h b/glib/gsequence.h new file mode 100644 index 000000000..d9bdd6d9c --- /dev/null +++ b/glib/gsequence.h @@ -0,0 +1,121 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 + * Soeren Sandmann (sandmann@daimi.au.dk) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#ifndef __G_SEQUENCE_H__ +#define __G_SEQUENCE_H__ + +typedef struct _GSequence GSequence; +typedef struct _GSequenceNode GSequenceIter; + +typedef gint (* GSequenceIterCompareFunc) (GSequenceIter *a, + GSequenceIter *b, + gpointer data); + + +/* GSequence */ +GSequence * g_sequence_new (GDestroyNotify data_destroy); +void g_sequence_free (GSequence *seq); +gint g_sequence_get_length (GSequence *seq); +void g_sequence_foreach (GSequence *seq, + GFunc func, + gpointer user_data); +void g_sequence_foreach_range (GSequenceIter *begin, + GSequenceIter *end, + GFunc func, + gpointer user_data); +void g_sequence_sort (GSequence *seq, + GCompareDataFunc cmp_func, + gpointer cmp_data); +void g_sequence_sort_iter (GSequence *seq, + GSequenceIterCompareFunc cmp_func, + gpointer cmp_data); + + +/* Getting iters */ +GSequenceIter *g_sequence_get_begin_iter (GSequence *seq); +GSequenceIter *g_sequence_get_end_iter (GSequence *seq); +GSequenceIter *g_sequence_get_iter_at_pos (GSequence *seq, + gint pos); +GSequenceIter *g_sequence_append (GSequence *seq, + gpointer data); +GSequenceIter *g_sequence_prepend (GSequence *seq, + gpointer data); +GSequenceIter *g_sequence_insert_before (GSequenceIter *iter, + gpointer data); +void g_sequence_move (GSequenceIter *src, + GSequenceIter *dest); +void g_sequence_swap (GSequenceIter *a, + GSequenceIter *b); +GSequenceIter *g_sequence_insert_sorted (GSequence *seq, + gpointer data, + GCompareDataFunc cmp_func, + gpointer cmp_data); +GSequenceIter *g_sequence_insert_sorted_iter (GSequence *seq, + gpointer data, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data); +void g_sequence_sort_changed (GSequenceIter *iter, + GCompareDataFunc cmp_func, + gpointer cmp_data); +void g_sequence_sort_changed_iter (GSequenceIter *iter, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data); +void g_sequence_remove (GSequenceIter *iter); +void g_sequence_remove_range (GSequenceIter *begin, + GSequenceIter *end); +void g_sequence_move_range (GSequenceIter *dest, + GSequenceIter *begin, + GSequenceIter *end); +GSequenceIter *g_sequence_search (GSequence *seq, + gpointer data, + GCompareDataFunc cmp_func, + gpointer cmp_data); +GSequenceIter *g_sequence_search_iter (GSequence *seq, + gpointer data, + GSequenceIterCompareFunc iter_cmp, + gpointer cmp_data); + + +/* Dereferencing */ +gpointer g_sequence_get (GSequenceIter *iter); +void g_sequence_set (GSequenceIter *iter, + gpointer data); + +/* Operations on GSequenceIter * */ +gboolean g_sequence_iter_is_begin (GSequenceIter *iter); +gboolean g_sequence_iter_is_end (GSequenceIter *iter); +GSequenceIter *g_sequence_iter_next (GSequenceIter *iter); +GSequenceIter *g_sequence_iter_prev (GSequenceIter *iter); +gint g_sequence_iter_get_position (GSequenceIter *iter); +GSequenceIter *g_sequence_iter_move (GSequenceIter *iter, + gint delta); +GSequence * g_sequence_iter_get_sequence (GSequenceIter *iter); + + +/* Search */ +gint g_sequence_iter_compare (GSequenceIter *a, + GSequenceIter *b); +GSequenceIter *g_sequence_range_get_midpoint (GSequenceIter *begin, + GSequenceIter *end); + + +#endif /* __G_SEQUENCE_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index ec438d45a..df5dd7b60 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ SUBDIRS=gobject refcount -INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/glib -I$(top_srcdir)/gmodule $(GLIB_DEBUG_FLAGS) +INCLUDES = -g -I$(top_srcdir) -I$(top_srcdir)/glib -I$(top_srcdir)/gmodule $(GLIB_DEBUG_FLAGS) EFENCE= @@ -93,6 +93,7 @@ test_programs = \ qsort-test \ rand-test \ relation-test \ + sequence-test \ shell-test \ slist-test \ slice-test \ @@ -160,6 +161,7 @@ asyncqueue_test_LDADD = $(thread_ldadd) qsort_test_LDADD = $(progs_ldadd) rand_test_LDADD = $(progs_ldadd) relation_test_LDADD = $(progs_ldadd) +sequence_test_LDADD = $(progs_ldadd) shell_test_LDADD = $(progs_ldadd) slist_test_LDADD = $(progs_ldadd) slice_test_SOURCES = slice-test.c memchunks.c diff --git a/tests/sequence-test.c b/tests/sequence-test.c new file mode 100644 index 000000000..026dddf46 --- /dev/null +++ b/tests/sequence-test.c @@ -0,0 +1,1153 @@ +#include +#include +#include + +enum + { + NEW, FREE, GET_LENGTH, FOREACH, FOREACH_RANGE, SORT, SORT_ITER, + + /* Getting iters */ + GET_BEGIN_ITER, GET_END_ITER, GET_ITER_AT_POS, APPEND, PREPEND, + INSERT_BEFORE, MOVE, INSERT_SORTED, INSERT_SORTED_ITER, SORT_CHANGED, + SORT_CHANGED_ITER, REMOVE, REMOVE_RANGE, MOVE_RANGE, SEARCH, SEARCH_ITER, + + /* dereferencing */ + GET, SET, + + /* operations on GSequenceIter * */ + ITER_IS_BEGIN, ITER_IS_END, ITER_NEXT, ITER_PREV, ITER_GET_POSITION, + ITER_MOVE, ITER_GET_SEQUENCE, + + /* search */ + ITER_COMPARE, RANGE_GET_MIDPOINT, + N_OPS + } Op; + +typedef struct SequenceInfo +{ + GQueue * queue; + GSequence * sequence; + int n_items; +} SequenceInfo; + +void g_sequence_self_test_internal_to_glib_dont_use (GSequence *sequence); + +static void +check_integrity (SequenceInfo *info) +{ + GList *list; + GSequenceIter *iter; + + g_sequence_self_test_internal_to_glib_dont_use (info->sequence); + + if (g_sequence_get_length (info->sequence) != info->n_items) + g_print ("%d %d\n", + g_sequence_get_length (info->sequence), info->n_items); + g_assert (info->n_items == g_queue_get_length (info->queue)); + g_assert (g_sequence_get_length (info->sequence) == info->n_items); + + iter = g_sequence_get_begin_iter (info->sequence); + list = info->queue->head; + while (iter != g_sequence_get_end_iter (info->sequence)) + { + g_assert (list->data == iter); + + iter = g_sequence_iter_next (iter); + list = list->next; + } + + g_assert (info->n_items == g_queue_get_length (info->queue)); + g_assert (g_sequence_get_length (info->sequence) == info->n_items); +} + +typedef struct +{ + SequenceInfo *seq; + int number; +} Item; + +static gpointer +new_item (SequenceInfo *seq) +{ + Item *item = g_new (Item, 1); + seq->n_items++; + item->seq = seq; + item->number = g_random_int (); + + /* There have been bugs in the past where the GSequence would + * dereference the user pointers. This will make sure such + * behavior causes crashes + */ + return ((char *)item + 1); +} + +static Item * +fix_pointer (gconstpointer data) +{ + return (Item *)((char *)data - 1); +} + +static Item * +get_item (GSequenceIter *iter) +{ + return fix_pointer (g_sequence_get (iter)); +} + +static void +free_item (gpointer data) +{ + Item *item = fix_pointer (data); + item->seq->n_items--; + g_free (item); +} + +static void +seq_foreach (gpointer data, + gpointer user_data) +{ + Item *item = fix_pointer (data); + GList **link = user_data; + GSequenceIter *iter; + + g_assert (*link != NULL); + + iter = (*link)->data; + + g_assert (get_item (iter) == item); + + item->number = g_random_int(); + + *link = (*link)->next; +} + +static gint +compare_items (gconstpointer a, + gconstpointer b, + gpointer data) +{ + const Item *item_a = fix_pointer (a); + const Item *item_b = fix_pointer (b); + + if (item_a->number < item_b->number) + return -1; + else if (item_a->number == item_b->number) + return 0; + else + return 1; +} + +static void +check_sorted (SequenceInfo *info) +{ + GList *list; + int last; + GSequenceIter *last_iter; + + check_integrity (info); + + last = G_MININT; + last_iter = NULL; + for (list = info->queue->head; list != NULL; list = list->next) + { + GSequenceIter *iter = list->data; + Item *item = get_item (iter); + + g_assert (item->number >= last); + /* Check that the ordering is the same as that of the queue, + * ie. that the sort is stable + */ + if (last_iter) + g_assert (iter == g_sequence_iter_next (last_iter)); + + last = item->number; + last_iter = iter; + } +} + +static gint +compare_iters (gconstpointer a, + gconstpointer b, + gpointer data) +{ + GSequenceIter *iter_a = (GSequenceIter *)a; + GSequenceIter *iter_b = (GSequenceIter *)b; + /* compare_items() will fix up the pointers */ + Item *item_a = g_sequence_get (iter_a); + Item *item_b = g_sequence_get (iter_b); + + return compare_items (item_a, item_b, data); +} + +/* A version of g_queue_link_index() that treats NULL as just + * beyond the queue + */ +static int +queue_link_index (SequenceInfo *seq, GList *link) +{ + if (link) + return g_queue_link_index (seq->queue, link); + else + return g_queue_get_length (seq->queue); +} + +static void +get_random_range (SequenceInfo *seq, + GSequenceIter **begin_iter, + GSequenceIter **end_iter, + GList **begin_link, + GList **end_link) +{ + int length = g_queue_get_length (seq->queue); + int b = g_random_int_range (0, length + 1); + int e = g_random_int_range (b, length + 1); + + g_assert (length == g_sequence_get_length (seq->sequence)); + + if (begin_iter) + *begin_iter = g_sequence_get_iter_at_pos (seq->sequence, b); + if (end_iter) + *end_iter = g_sequence_get_iter_at_pos (seq->sequence, e); + if (begin_link) + *begin_link = g_queue_peek_nth_link (seq->queue, b); + if (end_link) + *end_link = g_queue_peek_nth_link (seq->queue, e); + if (begin_iter && begin_link) + { + g_assert ( + queue_link_index (seq, *begin_link) == + g_sequence_iter_get_position (*begin_iter)); + } + if (end_iter && end_link) + { + g_assert ( + queue_link_index (seq, *end_link) == + g_sequence_iter_get_position (*end_iter)); + } +} + +static gint +get_random_position (SequenceInfo *seq) +{ + int length = g_queue_get_length (seq->queue); + + g_assert (length == g_sequence_get_length (seq->sequence)); + + return g_random_int_range (-2, length + 5); +} + +static GSequenceIter * +get_random_iter (SequenceInfo *seq, + GList **link) +{ + GSequenceIter *iter; + int pos = get_random_position (seq); + if (link) + *link = g_queue_peek_nth_link (seq->queue, pos); + iter = g_sequence_get_iter_at_pos (seq->sequence, pos); + if (link) + g_assert (queue_link_index (seq, *link) == g_sequence_iter_get_position (iter)); + return iter; +} + +static void +dump_info (SequenceInfo *seq) +{ +#if 0 + GSequenceIter *iter; + GList *list; + + iter = g_sequence_get_begin_iter (seq->sequence); + list = seq->queue->head; + + while (iter != g_sequence_get_end_iter (seq->sequence)) + { + Item *item = get_item (iter); + g_print ("%p %p %d\n", list->data, iter, item->number); + + iter = g_sequence_iter_next (iter); + list = list->next; + } +#endif +} + +/* A version of g_queue_insert_before() that appends if link is NULL */ +static void +queue_insert_before (SequenceInfo *seq, GList *link, gpointer data) +{ + if (link) + g_queue_insert_before (seq->queue, link, data); + else + g_queue_push_tail (seq->queue, data); +} + +static void +run_random_tests (guint32 seed) +{ +#define N_ITERATIONS 60000 +#define N_SEQUENCES 8 + + SequenceInfo sequences[N_SEQUENCES]; + int k; + + g_print (" seed: %u\n", seed); + + g_random_set_seed (seed); + + for (k = 0; k < N_SEQUENCES; ++k) + { + sequences[k].queue = g_queue_new (); + sequences[k].sequence = g_sequence_new (free_item); + sequences[k].n_items = 0; + } + +#define RANDOM_SEQUENCE() &(sequences[g_random_int_range (0, N_SEQUENCES)]) + + for (k = 0; k < N_ITERATIONS; ++k) + { + int i; + SequenceInfo *seq = RANDOM_SEQUENCE(); + int op = g_random_int_range (0, N_OPS); + +#if 0 + g_print ("%d\n", op); +#endif + + switch (op) + { + case NEW: + case FREE: + { + g_queue_free (seq->queue); + g_sequence_free (seq->sequence); + + g_assert (seq->n_items == 0); + + seq->queue = g_queue_new (); + seq->sequence = g_sequence_new (free_item); + + check_integrity (seq); + } + break; + case GET_LENGTH: + { + int slen = g_sequence_get_length (seq->sequence); + int qlen = g_queue_get_length (seq->queue); + + g_assert (slen == qlen); + } + break; + case FOREACH: + { + GList *link = seq->queue->head; + g_sequence_foreach (seq->sequence, seq_foreach, &link); + g_assert (link == NULL); + } + break; + case FOREACH_RANGE: + { + GSequenceIter *begin_iter, *end_iter; + GList *begin_link, *end_link; + + get_random_range (seq, &begin_iter, &end_iter, &begin_link, &end_link); + + check_integrity (seq); + + g_sequence_foreach_range (begin_iter, end_iter, seq_foreach, &begin_link); + + g_assert (begin_link == end_link); + } + break; + case SORT: + { + dump_info (seq); + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + dump_info (seq); + } + break; + case SORT_ITER: + { + g_sequence_sort_iter (seq->sequence, + (GSequenceIterCompareFunc)compare_iters, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + check_sorted (seq); + } + break; + + /* Getting iters */ + case GET_END_ITER: + case GET_BEGIN_ITER: + { + GSequenceIter *begin_iter; + GSequenceIter *end_iter; + GSequenceIter *penultimate_iter; + + begin_iter = g_sequence_get_begin_iter (seq->sequence); + check_integrity (seq); + + end_iter = g_sequence_get_end_iter (seq->sequence); + check_integrity (seq); + + penultimate_iter = g_sequence_iter_prev (end_iter); + check_integrity (seq); + + if (g_sequence_get_length (seq->sequence) > 0) + { + g_assert (seq->queue->head); + g_assert (seq->queue->head->data == begin_iter); + g_assert (seq->queue->tail); + g_assert (seq->queue->tail->data == penultimate_iter); + } + else + { + g_assert (penultimate_iter == end_iter); + g_assert (begin_iter == end_iter); + g_assert (penultimate_iter == begin_iter); + g_assert (seq->queue->head == NULL); + g_assert (seq->queue->tail == NULL); + } + } + break; + case GET_ITER_AT_POS: + { + int i; + + g_assert (g_queue_get_length (seq->queue) == g_sequence_get_length (seq->sequence)); + + for (i = 0; i < 10; ++i) + { + int pos = get_random_position (seq); + GSequenceIter *iter = g_sequence_get_iter_at_pos (seq->sequence, pos); + GList *link = g_queue_peek_nth_link (seq->queue, pos); + check_integrity (seq); + if (pos >= g_sequence_get_length (seq->sequence) || pos < 0) + { + g_assert (iter == g_sequence_get_end_iter (seq->sequence)); + g_assert (link == NULL); + } + else + { + g_assert (link); + g_assert (link->data == iter); + } + } + } + break; + case APPEND: + { + for (i = 0; i < 10; ++i) + { + GSequenceIter *iter = g_sequence_append (seq->sequence, new_item (seq)); + g_queue_push_tail (seq->queue, iter); + } + } + break; + case PREPEND: + { + for (i = 0; i < 10; ++i) + { + GSequenceIter *iter = g_sequence_prepend (seq->sequence, new_item (seq)); + g_queue_push_head (seq->queue, iter); + } + } + break; + case INSERT_BEFORE: + { + for (i = 0; i < 10; ++i) + { + GList *link; + GSequenceIter *iter = get_random_iter (seq, &link); + GSequenceIter *new_iter; + check_integrity (seq); + + new_iter = g_sequence_insert_before (iter, new_item (seq)); + + queue_insert_before (seq, link, new_iter); + } + } + break; + case MOVE: + { + GList *link1, *link2; + GSequenceIter *iter1 = get_random_iter (seq, &link1); + GSequenceIter *iter2 = get_random_iter (seq, &link2); + + if (!g_sequence_iter_is_end (iter1)) + { + g_sequence_move (iter1, iter2); + + if (!link2) + g_assert (g_sequence_iter_is_end (iter2)); + + queue_insert_before (seq, link2, link1->data); + + g_queue_delete_link (seq->queue, link1); + } + + check_integrity (seq); + + iter1 = get_random_iter (seq, NULL); + + /* Moving an iter to itself should have no effect */ + if (!g_sequence_iter_is_end (iter1)) + g_sequence_move (iter1, iter1); + } + break; + case INSERT_SORTED: + { + int i; + dump_info (seq); + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + for (i = 0; i < 15; ++i) + { + GSequenceIter *iter = + g_sequence_insert_sorted ( + seq->sequence, new_item(seq), compare_items, NULL); + + g_queue_insert_sorted (seq->queue, iter, compare_iters, NULL); + } + + check_sorted (seq); + + dump_info (seq); + } + break; + case INSERT_SORTED_ITER: + { + int i; + dump_info (seq); + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + for (i = 0; i < 15; ++i) + { + GSequenceIter *iter; + + iter = g_sequence_insert_sorted_iter (seq->sequence, + new_item (seq), + (GSequenceIterCompareFunc)compare_iters, + NULL); + + g_queue_insert_sorted (seq->queue, iter, compare_iters, NULL); + } + + check_sorted (seq); + + dump_info (seq); + } + break; + case SORT_CHANGED: + { + int i; + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + for (i = 0; i < 15; ++i) + { + GList *link; + GSequenceIter *iter = get_random_iter (seq, &link); + + if (!g_sequence_iter_is_end (iter)) + { + g_sequence_set (iter, new_item (seq)); + g_sequence_sort_changed (iter, compare_items, NULL); + + g_queue_delete_link (seq->queue, link); + g_queue_insert_sorted (seq->queue, iter, compare_iters, NULL); + } + + check_sorted (seq); + } + } + break; + case SORT_CHANGED_ITER: + { + int i; + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + for (i = 0; i < 15; ++i) + { + GList *link; + GSequenceIter *iter = get_random_iter (seq, &link); + + if (!g_sequence_iter_is_end (iter)) + { + g_sequence_set (iter, new_item (seq)); + g_sequence_sort_changed_iter (iter, + (GSequenceIterCompareFunc)compare_iters, NULL); + + g_queue_delete_link (seq->queue, link); + g_queue_insert_sorted (seq->queue, iter, compare_iters, NULL); + } + + check_sorted (seq); + } + } + break; + case REMOVE: + { + int i; + + for (i = 0; i < 15; ++i) + { + GList *link; + GSequenceIter *iter = get_random_iter (seq, &link); + + if (!g_sequence_iter_is_end (iter)) + { + g_sequence_remove (iter); + g_queue_delete_link (seq->queue, link); + } + } + } + break; + case REMOVE_RANGE: + { + GSequenceIter *begin_iter, *end_iter; + GList *begin_link, *end_link; + GList *list; + + get_random_range (seq, &begin_iter, &end_iter, &begin_link, &end_link); + + g_sequence_remove_range (begin_iter, end_iter); + + list = begin_link; + while (list != end_link) + { + GList *next = list->next; + + g_queue_delete_link (seq->queue, list); + + list = next; + } + } + break; + case MOVE_RANGE: + { + SequenceInfo *src = RANDOM_SEQUENCE(); + SequenceInfo *dst = RANDOM_SEQUENCE(); + + GSequenceIter *begin_iter, *end_iter; + GList *begin_link, *end_link; + + GSequenceIter *dst_iter; + GList *dst_link; + + GList *list; + + g_assert (src->queue); + g_assert (dst->queue); + + get_random_range (src, &begin_iter, &end_iter, &begin_link, &end_link); + dst_iter = get_random_iter (dst, &dst_link); + + g_sequence_move_range (dst_iter, begin_iter, end_iter); + + if (dst_link == begin_link || (src == dst && dst_link == end_link)) + { + check_integrity (src); + check_integrity (dst); + break; + } + + if (queue_link_index (src, begin_link) >= + queue_link_index (src, end_link)) + { + break; + } + + if (src == dst && + queue_link_index (src, dst_link) >= queue_link_index (src, begin_link) && + queue_link_index (src, dst_link) <= queue_link_index (src, end_link)) + { + break; + } + + list = begin_link; + while (list != end_link) + { + GList *next = list->next; + Item *item = get_item (list->data); + + g_assert (dst->queue); + queue_insert_before (dst, dst_link, list->data); + g_queue_delete_link (src->queue, list); + + g_assert (item->seq == src); + + src->n_items--; + dst->n_items++; + item->seq = dst; + + list = next; + } + } + break; + case SEARCH: + { + Item *item; + GSequenceIter *search_iter; + GSequenceIter *insert_iter; + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + item = new_item (seq); + search_iter = g_sequence_search (seq->sequence, item, compare_items, NULL); + + insert_iter = g_sequence_insert_sorted (seq->sequence, item, compare_items, NULL); + + g_assert (search_iter == g_sequence_iter_next (insert_iter)); + + g_queue_insert_sorted (seq->queue, insert_iter, compare_iters, NULL); + } + break; + case SEARCH_ITER: + { + Item *item; + GSequenceIter *search_iter; + GSequenceIter *insert_iter; + + g_sequence_sort (seq->sequence, compare_items, NULL); + g_queue_sort (seq->queue, compare_iters, NULL); + + check_sorted (seq); + + item = new_item (seq); + search_iter = g_sequence_search_iter (seq->sequence, + item, + (GSequenceIterCompareFunc)compare_iters, NULL); + + insert_iter = g_sequence_insert_sorted (seq->sequence, item, compare_items, NULL); + + g_assert (search_iter == g_sequence_iter_next (insert_iter)); + + g_queue_insert_sorted (seq->queue, insert_iter, compare_iters, NULL); + } + break; + + /* dereferencing */ + case GET: + case SET: + { + GSequenceIter *iter; + GList *link; + + iter = get_random_iter (seq, &link); + + if (!g_sequence_iter_is_end (iter)) + { + Item *item; + int i; + + check_integrity (seq); + + /* Test basic functionality */ + item = new_item (seq); + g_sequence_set (iter, item); + g_assert (g_sequence_get (iter) == item); + + /* Make sure that existing items are freed */ + for (i = 0; i < 15; ++i) + g_sequence_set (iter, new_item (seq)); + + check_integrity (seq); + + g_sequence_set (iter, new_item (seq)); + } + } + break; + + /* operations on GSequenceIter * */ + case ITER_IS_BEGIN: + { + GSequenceIter *iter; + + iter = g_sequence_get_iter_at_pos (seq->sequence, 0); + + g_assert (g_sequence_iter_is_begin (iter)); + + check_integrity (seq); + + if (g_sequence_get_length (seq->sequence) > 0) + { + g_assert (!g_sequence_iter_is_begin ( + g_sequence_get_end_iter (seq->sequence))); + } + else + { + g_assert (g_sequence_iter_is_begin ( + g_sequence_get_end_iter (seq->sequence))); + } + + g_assert (g_sequence_iter_is_begin (g_sequence_get_begin_iter (seq->sequence))); + } + break; + case ITER_IS_END: + { + GSequenceIter *iter; + int len = g_sequence_get_length (seq->sequence); + + iter = g_sequence_get_iter_at_pos (seq->sequence, len); + + g_assert (g_sequence_iter_is_end (iter)); + + if (len > 0) + { + g_assert (!g_sequence_iter_is_end ( + g_sequence_get_begin_iter (seq->sequence))); + } + else + { + g_assert (g_sequence_iter_is_end ( + g_sequence_get_begin_iter (seq->sequence))); + } + + g_assert (g_sequence_iter_is_end (g_sequence_get_end_iter (seq->sequence))); + } + break; + case ITER_NEXT: + { + GSequenceIter *iter1, *iter2, *iter3, *end; + + iter1 = g_sequence_append (seq->sequence, new_item (seq)); + iter2 = g_sequence_append (seq->sequence, new_item (seq)); + iter3 = g_sequence_append (seq->sequence, new_item (seq)); + + end = g_sequence_get_end_iter (seq->sequence); + + g_assert (g_sequence_iter_next (iter1) == iter2); + g_assert (g_sequence_iter_next (iter2) == iter3); + g_assert (g_sequence_iter_next (iter3) == end); + g_assert (g_sequence_iter_next (end) == end); + + g_queue_push_tail (seq->queue, iter1); + g_queue_push_tail (seq->queue, iter2); + g_queue_push_tail (seq->queue, iter3); + } + break; + case ITER_PREV: + { + GSequenceIter *iter1, *iter2, *iter3, *begin; + + iter1 = g_sequence_prepend (seq->sequence, new_item (seq)); + iter2 = g_sequence_prepend (seq->sequence, new_item (seq)); + iter3 = g_sequence_prepend (seq->sequence, new_item (seq)); + + begin = g_sequence_get_begin_iter (seq->sequence); + + g_assert (g_sequence_iter_prev (iter1) == iter2); + g_assert (g_sequence_iter_prev (iter2) == iter3); + g_assert (iter3 == begin); + g_assert (g_sequence_iter_prev (iter3) == begin); + g_assert (g_sequence_iter_prev (begin) == begin); + + g_queue_push_head (seq->queue, iter1); + g_queue_push_head (seq->queue, iter2); + g_queue_push_head (seq->queue, iter3); + } + break; + case ITER_GET_POSITION: + { + GList *link; + GSequenceIter *iter = get_random_iter (seq, &link); + + g_assert (g_sequence_iter_get_position (iter) == + queue_link_index (seq, link)); + } + break; + case ITER_MOVE: + { + int len = g_sequence_get_length (seq->sequence); + GSequenceIter *iter; + int pos; + + iter = get_random_iter (seq, NULL); + pos = g_sequence_iter_get_position (iter); + iter = g_sequence_iter_move (iter, len - pos); + g_assert (g_sequence_iter_is_end (iter)); + + + iter = get_random_iter (seq, NULL); + pos = g_sequence_iter_get_position (iter); + while (pos < len) + { + g_assert (!g_sequence_iter_is_end (iter)); + pos++; + iter = g_sequence_iter_move (iter, 1); + } + g_assert (g_sequence_iter_is_end (iter)); + } + break; + case ITER_GET_SEQUENCE: + { + GSequenceIter *iter = get_random_iter (seq, NULL); + + g_assert (g_sequence_iter_get_sequence (iter) == seq->sequence); + } + break; + + /* search */ + case ITER_COMPARE: + { + GList *link1, *link2; + GSequenceIter *iter1 = get_random_iter (seq, &link1); + GSequenceIter *iter2 = get_random_iter (seq, &link2); + + int cmp = g_sequence_iter_compare (iter1, iter2); + int pos1 = queue_link_index (seq, link1); + int pos2 = queue_link_index (seq, link2); + + if (cmp == 0) + { + g_assert (pos1 == pos2); + } + else if (cmp < 0) + { + g_assert (pos1 < pos2); + } + else + { + g_assert (pos1 > pos2); + } + } + break; + case RANGE_GET_MIDPOINT: + { + GSequenceIter *iter1 = get_random_iter (seq, NULL); + GSequenceIter *iter2 = get_random_iter (seq, NULL); + GSequenceIter *iter3; + int cmp; + + cmp = g_sequence_iter_compare (iter1, iter2); + + if (cmp > 0) + { + GSequenceIter *tmp; + + tmp = iter1; + iter1 = iter2; + iter2 = tmp; + } + + iter3 = g_sequence_range_get_midpoint (iter1, iter2); + + if (cmp == 0) + { + g_assert (iter3 == iter1); + g_assert (iter3 == iter2); + } + + g_assert (g_sequence_iter_get_position (iter3) >= + g_sequence_iter_get_position (iter1)); + g_assert (g_sequence_iter_get_position (iter2) >= + g_sequence_iter_get_position (iter3)); + } + break; + + } + + check_integrity (seq); + } +} + +/* Random seeds known to have failed at one point + */ +static gulong seeds[] = + { + 801678400u, + 1477639090u, + 3369132895u, + 1192944867u, + 770458294u, + 1099575817u, + 590523467u, + 3583571454u, + 579241222u + }; + +static void standalone_tests (void); + +static guint32 +get_seed (int argc, char **argv) +{ + if (argc > 1) + { + char *endptr; + + return strtol (argv[1], &endptr, 0); + } + else + { + return g_random_int(); + } +} + +int +main (int argc, + char **argv) +{ + guint32 seed = get_seed (argc, argv); + int i; + + /* Run stand alone tests */ + g_print ("running standalone tests\n"); + standalone_tests(); + + g_print ("running regression tests:\n"); + /* Run regression tests */ + for (i = 0; i < G_N_ELEMENTS (seeds); ++i) + { + run_random_tests (seeds[i]); + } + + /* Run with a new random seed */ + g_print ("random seed:\n"); + run_random_tests (seed); + + + return 0; +} + + +/* Single, stand-alone tests */ + +static void +test_out_of_range_jump (void) +{ + GSequence *seq = g_sequence_new (NULL); + GSequenceIter *iter = g_sequence_get_begin_iter (seq); + + g_sequence_iter_move (iter, 5); + + g_assert (g_sequence_iter_is_begin (iter)); + g_assert (g_sequence_iter_is_end (iter)); +} + +static int +compare (gconstpointer a, gconstpointer b, gpointer userdata) +{ + int ai, bi; + + ai = GPOINTER_TO_INT (a); + bi = GPOINTER_TO_INT (b); + + if (ai < bi) + return -1; + else if (ai > bi) + return 1; + else + return 0; +} + +static int +compare_iter (GSequenceIter *a, + GSequenceIter *b, + gpointer data) +{ + return compare (g_sequence_get (a), + g_sequence_get (b), + data); +} + +static void +test_insert_sorted_non_pointer (void) +{ + int i; + + for (i = 0; i < 10; i++) + { + GSequence *seq = g_sequence_new (NULL); + int j; + + for (j = 0; j < 10000; j++) + { + g_sequence_insert_sorted ( + seq, GINT_TO_POINTER (g_random_int()), + compare, NULL); + + g_sequence_insert_sorted_iter ( + seq, GINT_TO_POINTER (g_random_int()), + compare_iter, NULL); + } + + g_sequence_free (seq); + } +} + +static void +test_stable_sort (void) +{ + int i; + GSequence *seq = g_sequence_new (NULL); + +#define N_ITEMS 1000 + + GSequenceIter *iters[N_ITEMS]; + GSequenceIter *iter; + + for (i = 0; i < N_ITEMS; ++i) + iters[i] = g_sequence_append (seq, GINT_TO_POINTER (3000)); + + i = 0; + iter = g_sequence_get_begin_iter (seq); + while (!g_sequence_iter_is_end (iter)) + { + g_assert (iters[i++] == iter); + + iter = g_sequence_iter_next (iter); + } + + g_sequence_sort (seq, compare, NULL); + + i = 0; + iter = g_sequence_get_begin_iter (seq); + while (!g_sequence_iter_is_end (iter)) + { + g_assert (iters[i++] == iter); + + iter = g_sequence_iter_next (iter); + } + + for (i = N_ITEMS - 1; i >= 0; --i) + g_sequence_sort_changed (iters[i], compare, NULL); + + i = 0; + iter = g_sequence_get_begin_iter (seq); + while (!g_sequence_iter_is_end (iter)) + { + g_assert (iters[i++] == iter); + + iter = g_sequence_iter_next (iter); + } +} + +static void +standalone_tests (void) +{ + test_out_of_range_jump (); + test_insert_sorted_non_pointer (); + test_stable_sort (); +} +