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