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