f81c83c4f2
* NEW: Move gb.adt to main/lib/adt. It doesn't have extra dependencies git-svn-id: svn://localhost/gambas/trunk@5055 867c0c6c-44f3-4631-809d-bfa615b0a4ec
548 lines
12 KiB
C
548 lines
12 KiB
C
/*
|
|
* c_list.c - (Embedded) circular double-linked lists
|
|
*
|
|
* Copyright (C) 2012 Tobias Boege <tobias@gambas-buch.de>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#define __C_LIST_C
|
|
|
|
#include "gambas.h"
|
|
#include "c_list.h"
|
|
|
|
static DECLARE_CLIST_ROOT(free_nodes);
|
|
|
|
/* Component unload. Free all re-use nodes */
|
|
void CLIST_exit(void)
|
|
{
|
|
CLIST *node, *next;
|
|
|
|
clist_for_each_safe(node, &free_nodes, next)
|
|
GB.Free((void **) &node);
|
|
}
|
|
|
|
static int CLIST_is_root(CLIST *list)
|
|
{
|
|
/* Root nodes have their data member point to their structure. This
|
|
* means that they carry no information and that is pointless for
|
|
* real nodes. */
|
|
return list->data == list;
|
|
}
|
|
|
|
static int CLIST_is_empty(CLIST *list)
|
|
{
|
|
return list->next == list;
|
|
}
|
|
|
|
static int CLIST_is_linked(CLIST *node)
|
|
{
|
|
return node->has_link_ref;
|
|
}
|
|
|
|
static void CLIST_init(CLIST *node, void *data)
|
|
{
|
|
node->prev = node->next = node;
|
|
node->data = data;
|
|
}
|
|
|
|
/* Forward */
|
|
static CLIST *CLIST_extract(CLIST *, int);
|
|
static void CLIST_add_after(CLIST *, CLIST *);
|
|
static void CLIST_unlink(CLIST *);
|
|
|
|
static CLIST *CLIST_new(void *data)
|
|
{
|
|
CLIST *new;
|
|
|
|
/* Try to get a recycled node first */
|
|
if (!CLIST_is_empty(&free_nodes))
|
|
new = CLIST_extract(free_nodes.next, 1);
|
|
else
|
|
GB.Alloc((void **) &new, sizeof(*new));
|
|
CLIST_init(new, data);
|
|
return new;
|
|
}
|
|
|
|
static void *CLIST_destroy(CLIST *node)
|
|
{
|
|
void *data = node->data;
|
|
|
|
/* Recycle */
|
|
CLIST_unlink(node);
|
|
CLIST_add_after(&free_nodes, node);
|
|
return data;
|
|
}
|
|
|
|
static void CLIST_init_root(CLIST *node)
|
|
{
|
|
node->prev = node->next = node->data = node;
|
|
}
|
|
|
|
static CLIST *CLIST_new_root(void)
|
|
{
|
|
CLIST *new;
|
|
|
|
new = CLIST_new(NULL);
|
|
CLIST_init_root(new);
|
|
return new;
|
|
}
|
|
|
|
static void CLIST_destroy_root(CLIST *node)
|
|
{
|
|
CLIST_destroy(node);
|
|
}
|
|
|
|
#define THIS ((CLIST *) _object)
|
|
|
|
BEGIN_METHOD(List_new, GB_OBJECT obj; /* Usually 'Me' from container obj */
|
|
GB_BOOLEAN embedded) /* obj is container of THIS? */
|
|
|
|
int embedded = VARGOPT(embedded, 0);
|
|
|
|
CLIST_init(THIS, VARG(obj));
|
|
THIS->embedded = embedded;
|
|
THIS->has_link_ref = 0;
|
|
/* If the list embedded, we must not reference the container as that
|
|
* would cause not-so-easy to resolve circular references and thus
|
|
* memory errors. */
|
|
if (!embedded)
|
|
GB.Ref(THIS->data);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD_VOID(List_free)
|
|
|
|
if (!THIS->embedded)
|
|
GB.Unref(&THIS->data);
|
|
|
|
END_METHOD
|
|
|
|
static CLIST *CLIST_next_node(CLIST *node)
|
|
{
|
|
CLIST *next;
|
|
|
|
if (CLIST_is_empty(node))
|
|
return NULL;
|
|
|
|
next = node->next;
|
|
/* Erroneously more root nodes in the list? It should not be
|
|
* possible but catch it with that loop nevertheless. */
|
|
while (CLIST_is_root(next)) {
|
|
next = next->next;
|
|
if (next == node)
|
|
return NULL;
|
|
}
|
|
return next;
|
|
}
|
|
|
|
/* I prefer storing a pointer to this struct in GB.GetEnum() */
|
|
struct enum_state {
|
|
CLIST *first, *next; /* Be able to Unlink() in For Each */
|
|
};
|
|
|
|
/*
|
|
* We can either enumerate a list of embedded list nodes which have no root
|
|
* node or a list containing a root node which is then skipped.
|
|
* In both cases, we can enter the enumeration at any node in the list and
|
|
* all nodes in the list are enumerated.
|
|
*/
|
|
|
|
BEGIN_METHOD_VOID(List_next)
|
|
|
|
struct enum_state **statep = GB.GetEnum(), *state;
|
|
CLIST *cur;
|
|
|
|
state = *statep;
|
|
if (!state) { /* Beginning */
|
|
GB.Alloc((void **) statep, sizeof(*state));
|
|
state = *statep;
|
|
/* Catch the 'root' case and skip the node */
|
|
if (CLIST_is_root(THIS))
|
|
cur = CLIST_next_node(THIS);
|
|
else
|
|
cur = THIS;
|
|
if (!cur)
|
|
goto stop_enum;
|
|
state->first = cur;
|
|
goto done;
|
|
}
|
|
|
|
cur = state->next;
|
|
if (!cur || cur == state->first)
|
|
goto stop_enum;
|
|
|
|
done:
|
|
state->next = CLIST_next_node(cur);
|
|
GB.ReturnObject(cur->data);
|
|
return;
|
|
|
|
stop_enum:
|
|
GB.Free((void **) statep);
|
|
GB.StopEnum();
|
|
return;
|
|
|
|
END_METHOD
|
|
|
|
/*
|
|
* There are no particular 'nodes' when adding because each node is a fully
|
|
* capable list itself. When we pass a @new node, we always add the entire
|
|
* list it is currently linked to. In particular this is a yet unlinked list
|
|
* containing only one node in most cases but it is possible to create
|
|
* sub-lists individually and add some sub-lists to a global root later.
|
|
*/
|
|
|
|
static void CLIST_add_before(CLIST *list, CLIST *new)
|
|
{
|
|
CLIST *new_end = new->prev;
|
|
|
|
list->prev->next = new;
|
|
new->prev = list->prev;
|
|
new_end->next = list;
|
|
list->prev = new_end;
|
|
}
|
|
|
|
static void CLIST_add_after(CLIST *list, CLIST *new)
|
|
{
|
|
CLIST *new_end = new->prev;
|
|
|
|
new_end->next = list->next;
|
|
list->next->prev = new_end;
|
|
new->prev = list;
|
|
list->next = new;
|
|
}
|
|
|
|
static void CLIST_ref_once(CLIST *node)
|
|
{
|
|
if (!CLIST_is_linked(node)) {
|
|
node->has_link_ref = 1;
|
|
/* ListRoots do not receive ref. This makes almost automatic
|
|
* cleanup possible. The list may be fully linked but when
|
|
* the root loses its last ref, the list is cleared. */
|
|
if (CLIST_is_root(node))
|
|
return;
|
|
GB.Ref(node);
|
|
/* It is time now, at the latest, to Ref() the object of an
|
|
* embedded node because all other refs could get lost */
|
|
if (node->embedded)
|
|
GB.Ref(node->data);
|
|
}
|
|
}
|
|
|
|
static void CLIST_unref_once(CLIST **node)
|
|
{
|
|
if (CLIST_is_linked(*node)) {
|
|
(*node)->has_link_ref = 0;
|
|
if (CLIST_is_root(*node))
|
|
return;
|
|
/* Take the ref off from data of embedded node again */
|
|
if ((*node)->embedded)
|
|
GB.Unref(&(*node)->data);
|
|
GB.Unref((void **) node);
|
|
}
|
|
}
|
|
|
|
enum {
|
|
CLIST_BEFORE,
|
|
CLIST_AFTER
|
|
};
|
|
|
|
static void CLIST_add_and_ref(CLIST *node, CLIST *new, int mode)
|
|
{
|
|
if (mode == CLIST_BEFORE)
|
|
CLIST_add_before(node, new);
|
|
else /* This function is save from passing invalid modes */
|
|
CLIST_add_after(node, new);
|
|
/* Each node gets Ref()'d once when in a list. */
|
|
CLIST_ref_once(node);
|
|
CLIST_ref_once(new);
|
|
}
|
|
|
|
/* @node and @buf should be variables */
|
|
#define CHECK_ADD_ROOT(node, new, buf) \
|
|
do { \
|
|
CLIST *_new = (new); \
|
|
clist_for_each_first((node), _new, (buf)) { \
|
|
if (CLIST_is_root((node))) { \
|
|
GB.Error("Attempt to Add a root node."); \
|
|
return; \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
BEGIN_METHOD(List_AddPrev, GB_OBJECT new)
|
|
|
|
CLIST *new = (CLIST *) VARG(new), *node;
|
|
int buf;
|
|
|
|
CHECK_ADD_ROOT(node, new, buf);
|
|
CLIST_add_and_ref(THIS, new, CLIST_BEFORE);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD(List_AddNext, GB_OBJECT new)
|
|
|
|
CLIST *new = (CLIST *) VARG(new), *node;
|
|
int buf;
|
|
|
|
CHECK_ADD_ROOT(node, new, buf);
|
|
CLIST_add_and_ref(THIS, new, CLIST_AFTER);
|
|
|
|
END_METHOD
|
|
|
|
/*
|
|
* This operates on the exact node.
|
|
*/
|
|
|
|
static void CLIST_unlink(CLIST *node)
|
|
{
|
|
node->prev->next = node->next;
|
|
node->next->prev = node->prev;
|
|
/* Link to itself again to form a valid empty list */
|
|
node->prev = node->next = node;
|
|
}
|
|
|
|
static void CLIST_unlink_and_unref(CLIST *node)
|
|
{
|
|
CLIST *list;
|
|
|
|
/* list is only meaningful if the list gets empty after this
|
|
* Unlink() so it doesn't matter if THIS->prev or THIS->next
|
|
* is used. */
|
|
list = node->prev;
|
|
CLIST_unlink(node);
|
|
CLIST_unref_once(&node);
|
|
|
|
if (CLIST_is_empty(list))
|
|
CLIST_unref_once(&list);
|
|
}
|
|
|
|
#define CHECK_NOT_LINKED(node) \
|
|
do { \
|
|
if (!CLIST_is_linked(node)) { \
|
|
GB.Error("List node not linked"); \
|
|
return; \
|
|
} \
|
|
} while (0)
|
|
|
|
BEGIN_METHOD_VOID(List_Unlink)
|
|
|
|
CHECK_NOT_LINKED(THIS);
|
|
CLIST_unlink_and_unref(THIS);
|
|
|
|
END_METHOD
|
|
|
|
/*
|
|
* Extract a sub-list from a list. The @start gets returned if successful.
|
|
*/
|
|
|
|
static CLIST *CLIST_extract(CLIST *start, int count)
|
|
{
|
|
int i = count, buf;
|
|
CLIST *last;
|
|
|
|
if (!count)
|
|
return NULL;
|
|
|
|
clist_for_each_first(last, start, buf) {
|
|
if (!--i)
|
|
break;
|
|
}
|
|
/* Too few nodes in the entire list? */
|
|
if (i)
|
|
return NULL;
|
|
|
|
/* Unlink new list */
|
|
start->prev->next = last->next;
|
|
last->next->prev = start->prev;
|
|
/* Relink new list circularly to itself */
|
|
start->prev = last;
|
|
last->next = start;
|
|
return start;
|
|
}
|
|
|
|
BEGIN_METHOD(List_Extract, GB_INTEGER count)
|
|
|
|
int count = VARG(count);
|
|
CLIST *node;
|
|
CLIST *start;
|
|
|
|
if (!count) {
|
|
GB.Error(GB_ERR_ARG);
|
|
return;
|
|
}
|
|
|
|
CHECK_NOT_LINKED(THIS);
|
|
|
|
if (count == 1) { /* Is actually Unlink()? */
|
|
GB.ReturnObject(THIS); /* Prevent from vanish */
|
|
CLIST_unlink_and_unref(THIS);
|
|
} else { /* Real Extract() */
|
|
node = THIS->prev;
|
|
start = CLIST_extract(THIS, count);
|
|
if (!start) {
|
|
GB.Error(GB_ERR_BOUND);
|
|
return;
|
|
}
|
|
GB.ReturnObject(start);
|
|
/* Made old list empty? */
|
|
if (CLIST_is_empty(node))
|
|
CLIST_unref_once(&node);
|
|
}
|
|
|
|
END_METHOD
|
|
|
|
static void CLIST_unlink_and_unref_all(CLIST *list)
|
|
{
|
|
if (!CLIST_is_linked(list))
|
|
return;
|
|
|
|
while (!CLIST_is_empty(list))
|
|
CLIST_unlink_and_unref(list->next);
|
|
/* We get automatically Unref()'d by CLIST_unlink_and_unref() */
|
|
}
|
|
|
|
BEGIN_METHOD_VOID(List_Clear)
|
|
|
|
/*
|
|
* This must be called to prevent circular references at program
|
|
* termination. It is tricky to avoid circular references on
|
|
* circular linked lists. Therefore, alas, this function must be
|
|
* called explicitly or all containing nodes must be removed another
|
|
* way (see ListRoot_free).
|
|
*/
|
|
CLIST_unlink_and_unref_all(THIS);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_PROPERTY(List_Next)
|
|
|
|
GB.ReturnObject(THIS->next);
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(List_Previous)
|
|
|
|
GB.ReturnObject(THIS->prev);
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(List_IsLinked)
|
|
|
|
GB.ReturnBoolean(CLIST_is_linked(THIS));
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(List_IsEmbedded)
|
|
|
|
GB.ReturnBoolean(THIS->embedded);
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(List_Data)
|
|
|
|
if (READ_PROPERTY) {
|
|
GB.ReturnObject(THIS->data);
|
|
return;
|
|
}
|
|
if (THIS->embedded) {
|
|
GB.Error("Attempt to change Data on embedded node");
|
|
return;
|
|
}
|
|
GB.Unref((void **) &THIS->data);
|
|
THIS->data = VPROP(GB_OBJECT);
|
|
GB.Ref(THIS->data);
|
|
|
|
END_PROPERTY
|
|
|
|
GB_DESC CListDesc[] = {
|
|
GB_DECLARE("List", sizeof(CLIST)),
|
|
|
|
GB_CONSTANT("Embedded", "b", 1),
|
|
|
|
GB_METHOD("_new", NULL, List_new, "(Obj)o[(Embedded)b]"),
|
|
GB_METHOD("_free", NULL, List_free, NULL),
|
|
GB_METHOD("_next", "o", List_next, NULL),
|
|
|
|
GB_METHOD("AddPrev", NULL, List_AddPrev, "(Node)List;"),
|
|
GB_METHOD("AddNext", NULL, List_AddNext, "(Node)List;"),
|
|
GB_METHOD("Unlink", NULL, List_Unlink, NULL),
|
|
GB_METHOD("Extract", "List", List_Extract, "(Count)i"),
|
|
GB_METHOD("Clear", NULL, List_Clear, NULL),
|
|
|
|
GB_PROPERTY_READ("Next", "List", List_Next),
|
|
GB_PROPERTY_READ("Previous", "List", List_Previous),
|
|
GB_PROPERTY_READ("Prev", "List", List_Previous),
|
|
GB_PROPERTY_READ("IsLinked", "b", List_IsLinked),
|
|
GB_PROPERTY_READ("IsEmbedded", "b", List_IsEmbedded),
|
|
|
|
GB_PROPERTY("Data", "o", List_Data),
|
|
|
|
GB_END_DECLARE
|
|
};
|
|
|
|
BEGIN_METHOD_VOID(ListRoot_new)
|
|
|
|
CLIST_init_root(THIS);
|
|
THIS->embedded = 0;
|
|
THIS->has_link_ref = 0;
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD_VOID(ListRoot_free)
|
|
|
|
CLIST_unlink_and_unref_all(THIS);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_PROPERTY(ListRoot_IsEmpty)
|
|
|
|
GB.ReturnBoolean(CLIST_is_empty(THIS));
|
|
|
|
END_PROPERTY
|
|
|
|
GB_DESC CListRootDesc[] = {
|
|
GB_DECLARE("ListRoot", sizeof(CLIST)),
|
|
|
|
GB_METHOD("_new", NULL, ListRoot_new, NULL),
|
|
GB_METHOD("_free", NULL, ListRoot_free, NULL),
|
|
GB_METHOD("_next", "o", List_next, NULL),
|
|
|
|
GB_METHOD("AddEnd", NULL, List_AddPrev, "(Node)List;"),
|
|
GB_METHOD("AddStart", NULL, List_AddNext, "(Node)List;"),
|
|
GB_METHOD("Unlink", NULL, List_Unlink, NULL),
|
|
GB_METHOD("Extract", "List", List_Extract, "(Count)i"),
|
|
GB_METHOD("Clear", NULL, List_Clear, NULL),
|
|
|
|
GB_PROPERTY_READ("First", "List", List_Next),
|
|
GB_PROPERTY_READ("Last", "List", List_Previous),
|
|
GB_PROPERTY_READ("IsEmpty", "b", ListRoot_IsEmpty),
|
|
|
|
GB_END_DECLARE
|
|
};
|
|
|
|
CLIST_INTF List = {
|
|
.New = CLIST_new,
|
|
.Destroy = CLIST_destroy,
|
|
.NewRoot = CLIST_new_root,
|
|
.DestroyRoot = CLIST_destroy_root,
|
|
.IsRoot = CLIST_is_root,
|
|
.IsEmpty = CLIST_is_empty,
|
|
.AddBefore = CLIST_add_before,
|
|
.AddAfter = CLIST_add_after,
|
|
.Unlink = CLIST_unlink,
|
|
.Extract = CLIST_extract
|
|
};
|