core/sm_trie.cpp
author Borja Ferrer <faluco@alliedmods.net>
Fri Jan 05 00:42:22 2007 +0000 (2007-01-05)
changeset 268 d6fed4f05e07
parent 216 38b1bdbc5bc9
child 335 02cc96355b09
permissions -rw-r--r--
Fixed CPlayer init, it was called too early.
nothing important on the logger
dvander@189
     1
#include <string.h>
dvander@189
     2
#include <stdlib.h>
dvander@189
     3
#include <assert.h>
dvander@189
     4
#include "sm_trie.h"
dvander@189
     5
dvander@189
     6
/**
dvander@189
     7
 * Double Array Trie algorithm, based on:
dvander@189
     8
 * An Efficient Implementation of Trie Structures, by
dvander@189
     9
 *  Jun-ichi Aoe and Katsushi Maromoto, and Takashi Sato
dvander@189
    10
 * from Sofiware - Practice and Experience, Vol. 22(9), 695-721 (September 1992)
dvander@189
    11
 *
dvander@189
    12
 *  A Trie is a simple data structure which stores strings as DFAs, with each 
dvander@189
    13
 * transition state being a string entry.  For example, observe the following strings:
dvander@189
    14
 *
dvander@189
    15
 * BAILOPAN, BAT, BACON, BACK
dvander@189
    16
 *  These transition as the follow production rules:
dvander@189
    17
 *  B -> ...                  B
dvander@189
    18
 *       A -> ...             BA
dvander@189
    19
 *            I -> ...        BAI
dvander@189
    20
 *                 LOPAN      BAILOPAN
dvander@189
    21
 *            T -> ...        BAT
dvander@189
    22
 *            C ->            BAC
dvander@189
    23
 *                 O -> ...   BACO
dvander@189
    24
 *                      N     BACON
dvander@189
    25
 *                 K          BACK
dvander@189
    26
 *
dvander@189
    27
 *  The standard implementation for this - using lists - gives a slow linear lookup, somewhere between
dvander@189
    28
 * O(N+M) or O(log n).  A faster implementation is proposed in the paper above, which is based on compacting
dvander@189
    29
 * the transition states into two arrays.  In the paper's implementation, two arrays are used, and thus it is
dvander@189
    30
 * called the "Double Array" algorithm.  However, the CHECK array's size is maintained the same as BASE, 
dvander@189
    31
 * so they can be combined into one structure.  The array seems complex at first, but is very simple: it is a
dvander@189
    32
 * tree structure flattened out into a single vector.  I am calling this implementation the Flat Array Trie.
dvander@189
    33
 *
dvander@189
    34
 *  BASE[] is an array where each member is a node in the Trie.  The node can either be UNUSED (empty), an ARC
dvander@189
    35
 * (containing an offset to the next set of ARCs), or a TERMINATOR (contains the rest of a string).
dvander@189
    36
 * Each node has an index which must be interpeted based on the node type.  If the node is a TERMINATOR, then the
dvander@189
    37
 * index is an index into a string table, to find the rest of the string.  
dvander@189
    38
 *  If the node is an ARC, the index is another index into BASE.  For each possible token that can follow the
dvander@189
    39
 * current token, the value of those tokens can be added to the index given in the ARC.  Thus, given a current
dvander@189
    40
 * position and the next desired token, the current arc will jump to another arc which can contain either:
dvander@189
    41
 *   1) An invalid production (collision, no entry exists)
dvander@189
    42
 *   2) An empty production (no entry exists)
dvander@189
    43
 *   3) Another arc label (the string ends here or continues into more productions)
dvander@189
    44
 *   4) A TERMINATOR (the string ends here and contains an unused set of productions)
dvander@189
    45
 * 
dvander@189
    46
 *  So, given current offset N (starting at N=1), jumping to token C means the next offset will be:
dvander@189
    47
 *      offs = BASE[n] + C
dvander@189
    48
 *  Thus, the next node will be at:
dvander@189
    49
 *      BASE[BASE[n] + C]
dvander@189
    50
 * 
dvander@189
    51
 *  This allows each ARC to specify the base offset for any of its ARC children, like a tree.  Each node specifies
dvander@189
    52
 * its parent ARC -- so if an invalid offset is specified, the parent will not match, and thus no such derived 
dvander@189
    53
 * string exists.
dvander@189
    54
 *
dvander@189
    55
 *  This means that arrays can be laid out "sparsely," maximizing their usage.  Note that N need not be related to
dvander@189
    56
 * the range of tokens (1-256).  I.e., a base index does not have to be at 1, 256, 512, et cetera.  This is because
dvander@189
    57
 * insertion comes with a small deal of complexity.  To insert a new set of tokens T, the algorithm finds a new 
dvander@189
    58
 * BASE index N such that BASE[N+T[i]] is unused for each T[i].  Thus, indirection is not necessarily linear; 
dvander@189
    59
 * traversing a chain of ARC nodes can _and will_ jump around BASE.
dvander@189
    60
 *
dvander@189
    61
 *  Of course, given this level of flexibility in the array organization, there are collisions.  This is largely 
dvander@189
    62
 * where insertions become slow, as the old chain must be relocated before the new one is used.  Relocation means 
dvander@189
    63
 * finding one or more new base indexes, and this means traversing BASE until an acceptable index is found, such 
dvander@189
    64
 * that each offset is unused (see description in previous paragraph).
dvander@189
    65
 *
dvander@189
    66
 *  However, it is not insertion time we are concerned about.  The "trie" name comes from reTRIEval.  We are only
dvander@189
    67
 * concerned with lookup and deletion.  Both lookup and deletion are O(k), where k is relative to the length of the 
dvander@189
    68
 * input string.  Note that it is best case O(1) and worst case O(k).  Deleting the entire trie is always O(1).
dvander@189
    69
 */
dvander@189
    70
dvander@189
    71
/**
dvander@189
    72
 * Optimization ideas for the future:
dvander@189
    73
 * 1) Store a reference count for each arc, with the count of sub-children.
dvander@189
    74
 *    This could let us break out of "children searches" for case 4 easily.
dvander@189
    75
 * 2) Use a 'free list' so we can easily search the trie for free points.
dvander@189
    76
 *    This would drastically speed up x_check*
dvander@189
    77
 */
dvander@189
    78
dvander@189
    79
enum NodeType
dvander@189
    80
{
dvander@189
    81
	Node_Unused = 0,		/* Node is not being used (sparse) */
dvander@189
    82
	Node_Arc,				/* Node is part of an arc and does not terminate */
dvander@189
    83
	Node_Term,				/* Node is a terminator */
dvander@189
    84
};
dvander@189
    85
dvander@189
    86
struct TrieNode
dvander@189
    87
{
dvander@189
    88
	/**
dvander@189
    89
	 * For Node_Arc, this index stores the 'base' offset to the next arc chain.
dvander@189
    90
	 *   I.e. to jump from this arc to character C, it will be at base[idx+C].
dvander@189
    91
	 * For Node_Term, this is an index into the string table.
dvander@189
    92
	 */
dvander@189
    93
	unsigned int idx;
dvander@189
    94
dvander@189
    95
	/**
dvander@189
    96
	 * This contains the prior arc that we must have come from.
dvander@189
    97
	 * For example, if arc 63 has a base jump of index 12, and we want to see if
dvander@189
    98
	 * there is a valid character C, the parent of 12+C must be 63.
dvander@189
    99
	 */
dvander@189
   100
	unsigned int parent;
dvander@189
   101
dvander@189
   102
	void *value;			/* Value associated with this node */
dvander@189
   103
	NodeType mode;			/* Current usage type of the node */
dvander@189
   104
	bool valset;			/* Whether or not a value is set */
dvander@189
   105
};
dvander@189
   106
dvander@189
   107
struct Trie
dvander@189
   108
{
dvander@189
   109
	TrieNode *base;			/* Base array for the sparse tables */
dvander@189
   110
	char *stringtab;		/* String table pointer */
dvander@189
   111
	unsigned int baseSize;	/* Size of the base array, in members */
dvander@189
   112
	unsigned int stSize;	/* Size of the string table, in bytes */
dvander@189
   113
	unsigned int tail;		/* Current unused offset into the string table */
dvander@189
   114
};
dvander@189
   115
dvander@189
   116
inline unsigned char charval(char c)
dvander@189
   117
{
dvander@190
   118
	return (unsigned char)c;
dvander@189
   119
}
dvander@189
   120
dvander@193
   121
bool sm_trie_grow(Trie *trie)
dvander@193
   122
{
dvander@193
   123
	/* The current # of nodes in the tree is trie->baseSize + 1 */
dvander@193
   124
	unsigned int curSize = trie->baseSize;
dvander@193
   125
	unsigned int newSize = curSize * 2;
dvander@193
   126
dvander@193
   127
	//:TODO: Make functions calling this return failure if this fails 
dvander@193
   128
	trie->base = (TrieNode *)realloc(trie->base, (newSize + 1) * sizeof(TrieNode));
dvander@193
   129
	if (!trie->base)
dvander@193
   130
	{
dvander@193
   131
		return false;
dvander@193
   132
	}
dvander@193
   133
dvander@193
   134
	memset(&trie->base[curSize+1], 0, (newSize - curSize) * sizeof(TrieNode));
dvander@193
   135
	trie->baseSize = newSize;
dvander@193
   136
dvander@193
   137
	return true;
dvander@193
   138
}
dvander@193
   139
dvander@193
   140
unsigned int x_check(Trie *trie, char c, unsigned int start=1)
dvander@189
   141
{
dvander@189
   142
	TrieNode *base = trie->base;
dvander@189
   143
	unsigned char _c = charval(c);
dvander@189
   144
	unsigned int to_check = trie->baseSize - _c;
dvander@193
   145
	for (unsigned int i=start; i<=to_check; i++)
dvander@189
   146
	{
dvander@189
   147
		if (base[i+_c].mode == Node_Unused)
dvander@189
   148
		{
dvander@189
   149
			return i;
dvander@189
   150
		}
dvander@189
   151
	}
dvander@189
   152
dvander@193
   153
	sm_trie_grow(trie);
dvander@189
   154
dvander@193
   155
	return x_check(trie, c, to_check+1);
dvander@189
   156
}
dvander@189
   157
dvander@193
   158
unsigned int x_check2(Trie *trie, char c1, char c2, unsigned int start=1)
dvander@189
   159
{
dvander@189
   160
	TrieNode *base = trie->base;
dvander@189
   161
	unsigned char _c1 = charval(c1);
dvander@189
   162
	unsigned char _c2 = charval(c2);
dvander@189
   163
	unsigned int to_check = trie->baseSize - (_c1 > _c2 ? _c1 : _c2);
dvander@193
   164
	for (unsigned int i=start; i<=to_check; i++)
dvander@189
   165
	{
dvander@189
   166
		if (base[i+_c1].mode == Node_Unused
dvander@189
   167
			&& base[i+_c2].mode == Node_Unused)
dvander@189
   168
		{
dvander@189
   169
			return i;
dvander@189
   170
		}
dvander@189
   171
	}
dvander@189
   172
dvander@193
   173
	sm_trie_grow(trie);
dvander@189
   174
dvander@193
   175
	return x_check2(trie, c1, c2, to_check+1);
dvander@189
   176
}
dvander@189
   177
dvander@189
   178
unsigned int x_check_multi(Trie *trie, 
dvander@189
   179
							unsigned int offsets[], 
dvander@193
   180
							unsigned int count,
dvander@193
   181
							unsigned int start=1)
dvander@189
   182
{
dvander@189
   183
	TrieNode *base = trie->base;
dvander@189
   184
	TrieNode *cur;
dvander@189
   185
	unsigned int to_check = trie->baseSize;
dvander@193
   186
	unsigned int highest = 0;
dvander@193
   187
dvander@193
   188
	for (unsigned int i=0; i<count; i++)
dvander@193
   189
	{
dvander@193
   190
		if (offsets[i] > highest)
dvander@193
   191
		{
dvander@193
   192
			highest = offsets[i];
dvander@193
   193
		}
dvander@193
   194
	}
dvander@193
   195
dvander@193
   196
	to_check -= highest;
dvander@193
   197
dvander@193
   198
	for (unsigned int i=start; i<=to_check; i++)
dvander@189
   199
	{
dvander@189
   200
		bool okay = true;
dvander@189
   201
		for (unsigned int j=0; j<count; j++)
dvander@189
   202
		{
dvander@189
   203
			cur = &base[i+offsets[j]];
dvander@189
   204
			if (cur->mode != Node_Unused)
dvander@189
   205
			{
dvander@189
   206
				okay = false;
dvander@189
   207
				break;
dvander@189
   208
			}
dvander@189
   209
		}
dvander@189
   210
		if (okay)
dvander@189
   211
		{
dvander@189
   212
			return i;
dvander@189
   213
		}
dvander@189
   214
	}
dvander@189
   215
dvander@193
   216
	sm_trie_grow(trie);
dvander@189
   217
dvander@193
   218
	return x_check_multi(trie, offsets, count, to_check+1);
dvander@189
   219
}
dvander@189
   220
dvander@189
   221
unsigned int x_addstring(Trie *trie, const char *ptr)
dvander@189
   222
{
dvander@189
   223
	size_t len = strlen(ptr) + 1;
dvander@189
   224
dvander@193
   225
	if (trie->tail + len >= trie->stSize)
dvander@189
   226
	{		
dvander@189
   227
		while (trie->tail + len >= trie->stSize)
dvander@189
   228
		{
dvander@189
   229
			trie->stSize *= 2;
dvander@189
   230
		}
dvander@189
   231
		trie->stringtab = (char *)realloc(trie->stringtab, trie->stSize);
dvander@189
   232
	}
dvander@189
   233
dvander@189
   234
	unsigned int tail = trie->tail;
dvander@189
   235
	strcpy(&trie->stringtab[tail], ptr);
dvander@189
   236
	trie->tail += len;
dvander@189
   237
dvander@189
   238
	return tail;
dvander@189
   239
}
dvander@189
   240
dvander@189
   241
Trie *sm_trie_create()
dvander@189
   242
{
dvander@189
   243
	Trie *t = new Trie;
dvander@189
   244
dvander@193
   245
	t->base = (TrieNode *)malloc(sizeof(TrieNode) * (256 + 1));
dvander@189
   246
	t->stringtab = (char *)malloc(sizeof(char) * 256);
dvander@189
   247
	t->baseSize = 256;
dvander@189
   248
	t->stSize = 256;
dvander@189
   249
	t->tail = 0;
dvander@189
   250
dvander@193
   251
	memset(t->base, 0, sizeof(TrieNode) * (256 + 1));
dvander@189
   252
	memset(t->stringtab, 0, sizeof(char) * 256);
dvander@189
   253
dvander@189
   254
	/* Sentinel root node */
dvander@189
   255
	t->base[1].idx = 1;
dvander@189
   256
	t->base[1].mode = Node_Arc;
dvander@189
   257
	t->base[1].parent = 1;
dvander@189
   258
dvander@189
   259
	return t;
dvander@189
   260
}
dvander@189
   261
dvander@189
   262
void sm_trie_destroy(Trie *trie)
dvander@189
   263
{
dvander@189
   264
	free(trie->base);
dvander@189
   265
	free(trie->stringtab);
dvander@195
   266
	delete trie;
dvander@189
   267
}
dvander@189
   268
dvander@211
   269
bool sm_trie_delete(Trie *trie, const char *key)
dvander@211
   270
{
dvander@211
   271
	unsigned int lastidx = 1;		/* the last node index */
dvander@211
   272
	unsigned int curidx;			/* current node index */
dvander@211
   273
	const char *keyptr = key;		/* input stream at current token */
dvander@211
   274
	TrieNode *node = NULL;			/* current node being processed */
dvander@211
   275
	TrieNode *base = trie->base;
dvander@211
   276
dvander@211
   277
	if (!*key)
dvander@211
   278
	{
dvander@211
   279
		return false;
dvander@211
   280
	}
dvander@211
   281
dvander@211
   282
	/* Start traversing at the root node */
dvander@211
   283
	do
dvander@211
   284
	{
dvander@211
   285
		/* Find where the next character is, then advance */
dvander@211
   286
		curidx = base[lastidx].idx;
dvander@211
   287
		node = &base[curidx];
dvander@211
   288
		curidx += charval(*keyptr);
dvander@211
   289
		node = &base[curidx];
dvander@211
   290
		keyptr++;
dvander@211
   291
dvander@211
   292
		/* Check if this slot is supposed to be empty or is a collision */
dvander@211
   293
		if ((curidx > trie->baseSize) || node->mode == Node_Unused || node->parent != lastidx)
dvander@211
   294
		{
dvander@211
   295
			return false;
dvander@211
   296
		} else if (node->mode == Node_Term) {
dvander@211
   297
			char *term = &trie->stringtab[node->idx];
dvander@211
   298
			if (strcmp(keyptr, term) == 0)
dvander@211
   299
			{
dvander@211
   300
				break;
dvander@211
   301
			}
dvander@211
   302
		}
dvander@211
   303
		lastidx = curidx;
dvander@211
   304
	} while (*keyptr != '\0');
dvander@211
   305
dvander@211
   306
	assert(node != NULL);
dvander@211
   307
dvander@211
   308
	if (!node->valset)
dvander@211
   309
	{
dvander@211
   310
		return false;
dvander@211
   311
	}
dvander@211
   312
dvander@211
   313
	node->valset = false;
dvander@211
   314
	node->value = NULL;
dvander@211
   315
dvander@211
   316
	return true;
dvander@211
   317
}
dvander@211
   318
dvander@189
   319
bool sm_trie_retrieve(Trie *trie, const char *key, void **value)
dvander@189
   320
{
dvander@189
   321
	unsigned int lastidx = 1;		/* the last node index */
dvander@189
   322
	unsigned int curidx;			/* current node index */
dvander@189
   323
	const char *keyptr = key;		/* input stream at current token */
dvander@189
   324
	TrieNode *node = NULL;			/* current node being processed */
dvander@189
   325
	TrieNode *base = trie->base;
dvander@189
   326
dvander@189
   327
	if (!*key)
dvander@189
   328
	{
dvander@189
   329
		return false;
dvander@189
   330
	}
dvander@189
   331
dvander@189
   332
	/* Start traversing at the root node */
dvander@189
   333
	do
dvander@189
   334
	{
dvander@189
   335
		/* Find where the next character is, then advance */
dvander@189
   336
		curidx = base[lastidx].idx;
dvander@189
   337
		node = &base[curidx];
dvander@189
   338
		curidx += charval(*keyptr);
dvander@189
   339
		node = &base[curidx];
dvander@189
   340
		keyptr++;
dvander@189
   341
dvander@189
   342
		/* Check if this slot is supposed to be empty or is a collision */
dvander@194
   343
		if ((curidx > trie->baseSize) || node->mode == Node_Unused || node->parent != lastidx)
dvander@189
   344
		{
dvander@189
   345
			return false;
dvander@189
   346
		} else if (node->mode == Node_Term) {
dvander@189
   347
			char *term = &trie->stringtab[node->idx];
dvander@189
   348
			if (strcmp(keyptr, term) == 0)
dvander@189
   349
			{
dvander@189
   350
				break;
dvander@189
   351
			}
dvander@189
   352
		}
dvander@189
   353
		lastidx = curidx;
dvander@189
   354
	} while (*keyptr != '\0');
dvander@189
   355
dvander@189
   356
	assert(node != NULL);
dvander@189
   357
dvander@189
   358
	if (!node->valset)
dvander@189
   359
	{
dvander@189
   360
		return false;
dvander@189
   361
	}
dvander@189
   362
dvander@189
   363
	if (value)
dvander@189
   364
	{
dvander@189
   365
		*value = node->value;
dvander@189
   366
	}
dvander@189
   367
dvander@189
   368
	return true;
dvander@189
   369
}
dvander@189
   370
dvander@189
   371
bool sm_trie_insert(Trie *trie, const char *key, void *value)
dvander@189
   372
{
dvander@189
   373
	unsigned int lastidx = 1;		/* the last node index */
dvander@189
   374
	unsigned int curidx;			/* current node index */
dvander@189
   375
	const char *keyptr = key;		/* input stream at current token */
dvander@189
   376
	TrieNode *node = NULL;			/* current node being processed */
dvander@189
   377
	TrieNode *basenode = NULL;		/* current base node being processed */
dvander@189
   378
	unsigned int q;					/* temporary var for x_check results */
dvander@189
   379
	TrieNode *base = trie->base;
dvander@189
   380
	unsigned int curoffs;			/* current offset */
dvander@189
   381
dvander@189
   382
	/* Do not handle empty strings for simplicity */
dvander@189
   383
	if (!*key)
dvander@189
   384
	{
dvander@189
   385
		return false;
dvander@189
   386
	}
dvander@189
   387
dvander@189
   388
	/* Start traversing at the root node (1) */
dvander@189
   389
	do
dvander@189
   390
	{
dvander@189
   391
		/* Find where the next character is, then advance */
dvander@189
   392
		curidx = base[lastidx].idx;
dvander@189
   393
		basenode = &base[curidx];
dvander@189
   394
		curoffs = charval(*keyptr);
dvander@189
   395
		curidx += curoffs;
dvander@189
   396
		node = &base[curidx];
dvander@189
   397
		keyptr++;
dvander@189
   398
dvander@189
   399
		/* Check if this slot is supposed to be empty.  If so, we need to handle CASES 1/2:
dvander@189
   400
		 * Insertion without collisions
dvander@189
   401
		 */
dvander@194
   402
		if ( (curidx > trie->baseSize) || (node->mode == Node_Unused) )
dvander@189
   403
		{
dvander@194
   404
			if (curidx > trie->baseSize)
dvander@194
   405
			{
dvander@194
   406
				if (!sm_trie_grow(trie))
dvander@194
   407
				{
dvander@194
   408
					return false;
dvander@194
   409
				}
dvander@194
   410
				node = &trie->base[curidx];
dvander@194
   411
			}
dvander@189
   412
			node->parent = lastidx;
dvander@194
   413
			if (*keyptr == '\0')
dvander@194
   414
			{
dvander@194
   415
				node->mode = Node_Arc;
dvander@194
   416
			} else {
dvander@194
   417
				node->idx = x_addstring(trie, keyptr);
dvander@194
   418
				node->mode = Node_Term;
dvander@194
   419
			}
dvander@189
   420
			node->valset = true;
dvander@189
   421
			node->value = value;
dvander@189
   422
			
dvander@189
   423
			return true;
dvander@189
   424
		} else if (node->parent != lastidx) {
dvander@189
   425
			/* Collision! We have to split up the tree here.  CASE 4:
dvander@189
   426
			 * Insertion when a new word is inserted with a collision.
dvander@189
   427
			 * NOTE: This is the hardest case to handle.  All below examples are based on:
dvander@189
   428
			 * BACHELOR, BADGE, inserting BABY.
dvander@189
   429
			 * The problematic production here is A -> B, where B is already being used.
dvander@189
   430
			 *
dvander@189
   431
			 * This process has to rotate one half of the 'A' arc.  We generate two lists:
dvander@189
   432
			 *  Outgoing Arcs - Anything leaving this 'A'
dvander@189
   433
			 *  Incoming Arcs - Anything going to this 'A'
dvander@189
   434
			 * Whichever list is smaller will be moved.  Note that this works because the intersection
dvander@189
   435
			 * affects both arc chains, and moving one will make the slot available to either.
dvander@189
   436
			 */
dvander@189
   437
			TrieNode *cur;
dvander@189
   438
			
dvander@189
   439
			/* Find every node arcing from the last node.
dvander@189
   440
			 * I.e. for BACHELOR, BADGE, BABY,
dvander@189
   441
			 * The arcs leaving A will be C and D, but our current node is B -> *.
dvander@189
   442
			 * Thus, we use the last index (A) to find the base for arcs leaving A.
dvander@189
   443
			 */
dvander@189
   444
			unsigned int outgoing_base = base[lastidx].idx;
dvander@189
   445
			unsigned int outgoing_list[256];
dvander@189
   446
			unsigned int outgoing_count = 0;	/* count the current index here */
dvander@189
   447
			cur = &base[outgoing_base] + 1;
dvander@194
   448
			unsigned int outgoing_limit = 255;
dvander@194
   449
dvander@194
   450
			if (outgoing_base + outgoing_limit > trie->baseSize)
dvander@194
   451
			{
dvander@194
   452
				outgoing_limit = trie->baseSize - outgoing_base;
dvander@194
   453
			}
dvander@194
   454
dvander@194
   455
			for (unsigned int i=1; i<=outgoing_limit; i++,cur++)
dvander@189
   456
			{
dvander@189
   457
				if (cur->mode == Node_Unused || cur->parent != lastidx)
dvander@189
   458
				{
dvander@189
   459
					continue;
dvander@189
   460
				}
dvander@189
   461
				outgoing_list[outgoing_count++] = i;
dvander@189
   462
			}
dvander@189
   463
			outgoing_list[outgoing_count++] = curidx - outgoing_base;
dvander@189
   464
dvander@189
   465
			/* Now we need to find all the arcs leaving our parent...
dvander@189
   466
			 * Note: the inconsistency is the base of our parent.  
dvander@189
   467
			 */
dvander@193
   468
			assert(base[node->parent].mode == Node_Arc);
dvander@189
   469
			unsigned int incoming_list[256];
dvander@189
   470
			unsigned int incoming_base = base[node->parent].idx;
dvander@189
   471
			unsigned int incoming_count = 0;
dvander@194
   472
			unsigned int incoming_limit = 255;
dvander@189
   473
			cur = &base[incoming_base] + 1;
dvander@194
   474
dvander@194
   475
			if (incoming_base + incoming_limit > trie->baseSize)
dvander@194
   476
			{
dvander@195
   477
				incoming_limit = trie->baseSize - incoming_base;
dvander@194
   478
			}
dvander@194
   479
dvander@195
   480
			assert(incoming_limit > 0 && incoming_limit <= 255);
dvander@195
   481
dvander@189
   482
			for (unsigned int i=1; i<=255; i++,cur++)
dvander@189
   483
			{
dvander@189
   484
				if (cur->mode == Node_Arc || cur->mode == Node_Term)
dvander@189
   485
				{
dvander@189
   486
					if (cur->parent == node->parent)
dvander@189
   487
					{
dvander@189
   488
						incoming_list[incoming_count++] = i;
dvander@189
   489
					}
dvander@189
   490
				}
dvander@189
   491
			}
dvander@189
   492
dvander@189
   493
			if (incoming_count < outgoing_count + 1)
dvander@189
   494
			{
dvander@189
   495
				unsigned int q = x_check_multi(trie, incoming_list, incoming_count);
dvander@189
   496
dvander@193
   497
				base = trie->base;
dvander@193
   498
				node = &base[curidx];
dvander@193
   499
dvander@189
   500
				/* If we're incoming, we need to modify our parent */
dvander@193
   501
				base[node->parent].idx = q;
dvander@189
   502
dvander@189
   503
				/* For each node in the "to move" list,
dvander@189
   504
				 * Relocate the node's info to the new position.
dvander@189
   505
				 */
dvander@189
   506
				unsigned int idx, newidx, oldidx;
dvander@189
   507
				for (unsigned int i=0; i<incoming_count; i++)
dvander@189
   508
				{
dvander@189
   509
					idx = incoming_list[i];
dvander@189
   510
					newidx = q + idx;
dvander@189
   511
					oldidx = incoming_base + idx;
dvander@189
   512
					if (oldidx == lastidx)
dvander@189
   513
					{
dvander@189
   514
						/* Important! Make sure we're not invalidating our sacred lastidx */
dvander@189
   515
						lastidx = newidx;
dvander@189
   516
					}
dvander@189
   517
					base[newidx] = base[oldidx];
dvander@194
   518
					assert(base[base[newidx].parent].mode == Node_Arc);
dvander@189
   519
					memset(&base[oldidx], 0, sizeof(TrieNode));
dvander@189
   520
					/* If we are not a terminator, we have children we must take care of */
dvander@189
   521
					if (base[newidx].mode == Node_Arc)
dvander@189
   522
					{
dvander@189
   523
						TrieNode *check_base = &base[base[newidx].idx] + 1;
dvander@193
   524
						for (unsigned int j=1; j<=255; j++, check_base++)
dvander@189
   525
						{
dvander@189
   526
							if (check_base->parent == oldidx)
dvander@189
   527
							{
dvander@189
   528
								check_base->parent = newidx;
dvander@189
   529
							}
dvander@189
   530
						}
dvander@189
   531
					}
dvander@189
   532
				}
dvander@189
   533
			} else {
dvander@189
   534
				unsigned int q = x_check_multi(trie, outgoing_list, outgoing_count);
dvander@189
   535
dvander@193
   536
				base = trie->base;
dvander@193
   537
				node = &base[curidx];
dvander@193
   538
dvander@189
   539
				/* If we're outgoing, we need to modify our own base */
dvander@189
   540
				base[lastidx].idx = q;
dvander@189
   541
dvander@189
   542
				/* Take the last index (curidx) out of the list.  Technically we are not moving this,
dvander@189
   543
				 * since it's already being used by something else.  
dvander@189
   544
				 */
dvander@189
   545
				outgoing_count--;
dvander@189
   546
dvander@189
   547
				/* For each node in the "to move" list,
dvander@189
   548
				 * Relocate the node's info to the new position.
dvander@189
   549
				 */
dvander@189
   550
				unsigned int idx, newidx, oldidx;
dvander@189
   551
				for (unsigned int i=0; i<outgoing_count; i++)
dvander@189
   552
				{
dvander@189
   553
					idx = outgoing_list[i];
dvander@189
   554
					newidx = q + idx;
dvander@189
   555
					oldidx = outgoing_base + idx;
dvander@189
   556
					if (oldidx == lastidx)
dvander@189
   557
					{
dvander@189
   558
						/* Important! Make sure we're not invalidating our sacred lastidx */
dvander@189
   559
						lastidx = newidx;
dvander@189
   560
					}
dvander@189
   561
					base[newidx] = base[oldidx];
dvander@194
   562
					assert(base[base[newidx].parent].mode == Node_Arc);
dvander@189
   563
					memset(&base[oldidx], 0, sizeof(TrieNode));
dvander@189
   564
					/* If we are not a terminator, we have children we must take care of */
dvander@189
   565
					if (base[newidx].mode == Node_Arc)
dvander@189
   566
					{
dvander@189
   567
						TrieNode *check_base = &base[base[newidx].idx] + 1;
dvander@193
   568
						for (unsigned int j=1; j<=255; j++, check_base++)
dvander@189
   569
						{
dvander@189
   570
							if (check_base->parent == oldidx)
dvander@189
   571
							{
dvander@189
   572
								check_base->parent = newidx;
dvander@189
   573
							}
dvander@189
   574
						}
dvander@189
   575
					}
dvander@189
   576
				}
dvander@189
   577
dvander@189
   578
				/* Take the invisible node and use it as our new node */
dvander@189
   579
				node = &base[q + outgoing_list[outgoing_count]];
dvander@189
   580
			}
dvander@189
   581
dvander@189
   582
			/* We're finally done! */
dvander@189
   583
			node->parent = lastidx;
dvander@189
   584
			if (*keyptr == '\0')
dvander@189
   585
			{
dvander@189
   586
				node->mode = Node_Arc;
dvander@189
   587
			} else {
dvander@189
   588
				node->idx = x_addstring(trie, keyptr);
dvander@189
   589
				node->mode = Node_Term;
dvander@189
   590
			}
dvander@189
   591
			node->valset = true;
dvander@189
   592
			node->value = value;
dvander@189
   593
dvander@189
   594
			return true;
dvander@189
   595
		} else {
dvander@189
   596
			/* See what's in the next node - special case if terminator! */
dvander@189
   597
			if (node->mode == Node_Term)
dvander@189
   598
			{
dvander@189
   599
				/* If we're a terminator, we need to handle CASE 3:
dvander@189
   600
				 * Insertion when a terminating collision occurs
dvander@189
   601
				 */
dvander@189
   602
				char *term = &trie->stringtab[node->idx];
dvander@189
   603
				/* Do an initial browsing to make sure they're not the same string */
dvander@189
   604
				if (strcmp(keyptr, term) == 0)
dvander@189
   605
				{
faluco@216
   606
					if (!node->valset)
faluco@216
   607
					{
faluco@216
   608
						node->valset = true;
faluco@216
   609
						node->value = value;
faluco@216
   610
						return true;
faluco@216
   611
					}
dvander@189
   612
					/* Same string.  We can't insert. */
dvander@189
   613
					return false;
dvander@189
   614
				}
dvander@189
   615
				/* For each matching character pair, we need to disband the terminator.
dvander@189
   616
				 * This splits the similar prefix into a single arc path.
dvander@189
   617
				 * First, save the old values so we can move them to a new node.
dvander@189
   618
				 * Next, for each loop:
dvander@189
   619
				 *  Take the current (invalid) node, and point it to the next arc base.
dvander@189
   620
				 *  Set the current node to the node at the next arc.
dvander@189
   621
				 */
dvander@189
   622
				void *oldvalue = node->value;
dvander@189
   623
				bool oldvalset = node->valset;
dvander@193
   624
				if (*term == *keyptr)
dvander@193
   625
				{
dvander@189
   626
				while (*term == *keyptr)
dvander@189
   627
				{
dvander@189
   628
					/* Find the next free slot in the check array.
dvander@189
   629
					 * This is the "vector base" essentially
dvander@189
   630
					 */
dvander@189
   631
					q = x_check(trie, *term);
dvander@193
   632
						base = trie->base;
dvander@193
   633
						node = &base[curidx];
dvander@189
   634
					/* Point the node to the next new base */
dvander@189
   635
					node->idx = q;
dvander@189
   636
					node->mode = Node_Arc;
dvander@189
   637
					node->valset = false;
dvander@189
   638
					/* Advance the input stream and local variables */
dvander@189
   639
					lastidx = curidx;
dvander@189
   640
					curidx = q + charval(*term);
dvander@189
   641
					node = &base[curidx];
dvander@189
   642
					/* Make sure the new current node has its parent set. */
dvander@189
   643
					node->parent = lastidx;
dvander@189
   644
					node->mode = Node_Arc;	/* Just in case we run x_check again */
dvander@189
   645
					*term = '\0';	/* Unmark the string table here */
dvander@189
   646
					term++;
dvander@189
   647
					keyptr++;
dvander@193
   648
					}
dvander@193
   649
				} else {
dvander@193
   650
					node->valset = false;
dvander@189
   651
				}
dvander@189
   652
				/* We're done inserting new pairs.  If one of them is exhausted,
dvander@189
   653
				 * we take special shortcuts.
dvander@189
   654
				 */
dvander@189
   655
				if (*term == '\0')				//EX: BADGERHOUSE added over B -> ADGER.  
dvander@189
   656
				{
dvander@189
   657
					/* First backpatch the current node - it ends the newly split terminator.
dvander@189
   658
					 * In the example, this would mean the node is the production from R -> ?
dvander@189
   659
					 * This node ends the old BADGER, so we set it here.
dvander@189
   660
					 */
dvander@189
   661
					node->valset = oldvalset;
dvander@189
   662
					node->value = oldvalue;
dvander@189
   663
dvander@189
   664
					/* The terminator was split up, but pieces of keyptr remain.
dvander@189
   665
					 * We need to generate a new production, in this example, R -> H,
dvander@189
   666
					 * with H being a terminator to OUSE.  Thus we get:
dvander@189
   667
					 * B,A,D,G,E,R*,H*->OUSE (* = value set).
dvander@189
   668
					 * NOTE: parent was last set at the end of the while loop.
dvander@189
   669
					 */
dvander@193
   670
					/* Get the new base and apply re-basing */
dvander@189
   671
					q = x_check(trie, *keyptr);
dvander@193
   672
					base = trie->base;
dvander@193
   673
					node = &base[curidx];
dvander@193
   674
dvander@189
   675
					node->idx = q;
dvander@189
   676
					node->mode = Node_Arc;
dvander@189
   677
					lastidx = curidx;
dvander@189
   678
					/* Finish the final node */
dvander@189
   679
					curidx = q + charval(*keyptr);
dvander@189
   680
					node = &trie->base[curidx];
dvander@189
   681
					keyptr++;
dvander@189
   682
					/* Optimize - don't add to string table if there's nothing more to eat */
dvander@189
   683
					if (*keyptr == '\0')
dvander@189
   684
					{
dvander@189
   685
						node->mode = Node_Arc;
dvander@189
   686
					} else {
dvander@189
   687
						node->idx = x_addstring(trie, keyptr);
dvander@189
   688
						node->mode = Node_Term;
dvander@189
   689
					}
dvander@189
   690
					node->parent = lastidx;
dvander@189
   691
					node->valset = true;
dvander@189
   692
					node->value = value;
dvander@189
   693
				} else if (*keyptr == '\0') {	//EX: BADGER added over B -> ADGERHOUSE
dvander@189
   694
					/* First backpatch the current node - it ends newly split input string.
dvander@189
   695
					 * This is the exact opposite of the above procedure.
dvander@189
   696
					 */
dvander@189
   697
					node->valset = true;
dvander@189
   698
					node->value = value;
dvander@189
   699
dvander@193
   700
					/* Get the new base and apply re-basing */
dvander@189
   701
					q = x_check(trie, *term);
dvander@193
   702
					base = trie->base;
dvander@193
   703
					node = &base[curidx];
dvander@193
   704
dvander@189
   705
					node->idx = q;
dvander@189
   706
					node->mode = Node_Arc;
dvander@189
   707
					lastidx = curidx;
dvander@189
   708
					/* Finish the final node */
dvander@189
   709
					curidx = q + charval(*term);
dvander@189
   710
					node = &trie->base[curidx];
dvander@189
   711
					term++;
dvander@189
   712
					/* Optimize - don't add to string table if there's nothing more to eat */
dvander@189
   713
					if (*term == '\0')
dvander@189
   714
					{
dvander@189
   715
						node->mode = Node_Arc;
dvander@189
   716
					} else {
dvander@189
   717
						node->idx = (term - trie->stringtab); /* Already in the string table! */
dvander@189
   718
						node->mode = Node_Term;
dvander@189
   719
					}
dvander@189
   720
					node->parent = lastidx;
dvander@189
   721
					node->valset = oldvalset;
dvander@189
   722
					node->value = oldvalue;
dvander@189
   723
				} else {
dvander@189
   724
					/* Finally, we have to create two new nodes instead of just one. */
dvander@189
   725
					node->mode = Node_Arc;
dvander@189
   726
					
dvander@193
   727
					/* Get the new base and apply re-basing */
dvander@189
   728
					q = x_check2(trie, *keyptr, *term);
dvander@193
   729
					base = trie->base;
dvander@193
   730
					node = &base[curidx];
dvander@193
   731
dvander@189
   732
					node->idx = q;
dvander@189
   733
					lastidx = curidx;
dvander@189
   734
dvander@189
   735
					/* Re-create the old terminated node */
dvander@189
   736
					curidx = q + charval(*term);
dvander@189
   737
					node = &trie->base[curidx];
dvander@189
   738
					term++;
dvander@189
   739
					node->valset = oldvalset;
dvander@189
   740
					node->value = oldvalue;
dvander@189
   741
					node->parent = lastidx;
dvander@189
   742
					if (*term == '\0')
dvander@189
   743
					{
dvander@189
   744
						node->mode = Node_Arc;
dvander@189
   745
					} else {
dvander@189
   746
						node->mode = Node_Term;
dvander@189
   747
						node->idx = (term - trie->stringtab); /* Already in the string table! */
dvander@189
   748
					}
dvander@189
   749
dvander@189
   750
					/* Create the new keyed input node */
dvander@189
   751
					curidx = q + charval(*keyptr);
dvander@189
   752
					node = &trie->base[curidx];
dvander@189
   753
					keyptr++;
dvander@189
   754
					node->valset = true;
dvander@189
   755
					node->value = value;
dvander@189
   756
					node->parent = lastidx;
dvander@189
   757
					if (*keyptr == '\0')
dvander@189
   758
					{
dvander@189
   759
						node->mode = Node_Arc;
dvander@189
   760
					} else {
dvander@189
   761
						node->mode = Node_Term;
dvander@189
   762
						node->idx = x_addstring(trie, keyptr);
dvander@189
   763
					}
dvander@189
   764
				}
dvander@189
   765
dvander@189
   766
				/* Phew! */
dvander@189
   767
				return true;
dvander@195
   768
			} else {
dvander@195
   769
				assert(node->mode == Node_Arc);
dvander@189
   770
			}
dvander@189
   771
		}
dvander@189
   772
		lastidx = curidx;
dvander@189
   773
	} while (*keyptr != '\0');
dvander@189
   774
dvander@189
   775
	assert(node);
dvander@189
   776
dvander@189
   777
	/* If we've exhausted the string and we have a valid reached node,
dvander@189
   778
	 * the production rule already existed.  Make sure it's valid to set first.
dvander@189
   779
	 */
dvander@189
   780
dvander@189
   781
	/* We have to be an Arc.  If the last result was anything else, we would have returned a new 
dvander@189
   782
	 * production earlier.
dvander@189
   783
	 */
dvander@189
   784
	assert(node->mode == Node_Arc);
dvander@189
   785
dvander@189
   786
	/* Furthermore, if this is the last arc label in an arc, we should have a value set. */
dvander@189
   787
	if (node->idx == 0)
dvander@189
   788
	{
dvander@189
   789
		assert(node->valset == true);
dvander@189
   790
	}
dvander@189
   791
dvander@189
   792
	if (!node->valset)
dvander@189
   793
	{
dvander@189
   794
		/* Insert is only possible if we have no production */
dvander@189
   795
		node->valset = true;
dvander@189
   796
		node->value = value;
dvander@189
   797
		return true;
dvander@189
   798
	}
dvander@189
   799
dvander@189
   800
	return false;
dvander@189
   801
}