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