mirror of
https://github.com/libretro/libretro-tyrquake.git
synced 2024-11-24 16:39:43 +00:00
5bfd94281e
Remove all inserts for the old completion tree and remove the rest of the api and functions from shell.[ch]. Signed-off-by: Tyrann <tyrann@disenchant.net>
466 lines
11 KiB
C
466 lines
11 KiB
C
/*
|
|
Copyright (C) 2002 Kevin Shanahan
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
* This whole setup is butt-ugly. Proceed with caution.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "console.h"
|
|
#include "cvar.h"
|
|
#include "mathlib.h"
|
|
#include "rb_tree.h"
|
|
#include "shell.h"
|
|
#include "sys.h"
|
|
#include "zone.h"
|
|
|
|
/*
|
|
* When we want to build up temporary trees of strings for completions, file
|
|
* listings, etc. we can use the "temp" hunk since we don't want to keep them
|
|
* around. These allocator functions attempt to make more efficient use of the
|
|
* hunk space by keeping the tree nodes together in blocks and allocating
|
|
* strings right next to each other.
|
|
*/
|
|
static struct stree_node *st_node_next;
|
|
static int st_node_space;
|
|
static char *st_string_next;
|
|
static int st_string_space;
|
|
|
|
#define ST_NODE_CHUNK 2048 /* 2kB / 16B => 128 nodes */
|
|
#define ST_STRING_CHUNK 4096 /* 4k of strings together */
|
|
|
|
void
|
|
STree_AllocInit(void)
|
|
{
|
|
/* Init the temp hunk */
|
|
st_node_next = Hunk_TempAlloc(ST_NODE_CHUNK);
|
|
st_node_space = ST_NODE_CHUNK;
|
|
|
|
/* Allocate string space on demand */
|
|
st_string_space = 0;
|
|
}
|
|
|
|
static struct stree_node *
|
|
STree_AllocNode(void)
|
|
{
|
|
struct stree_node *ret = NULL;
|
|
|
|
if (st_node_space < sizeof(struct stree_node)) {
|
|
st_node_next = Hunk_TempAllocExtend(ST_NODE_CHUNK);
|
|
st_node_space = st_node_next ? ST_NODE_CHUNK : 0;
|
|
}
|
|
if (st_node_space >= sizeof(struct stree_node)) {
|
|
ret = st_node_next++;
|
|
st_node_space -= sizeof(struct stree_node);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *
|
|
STree_AllocString(unsigned int length)
|
|
{
|
|
char *ret = NULL;
|
|
|
|
if (st_string_space < length) {
|
|
/*
|
|
* Note: might want to consider different allocation scheme here if we
|
|
* end up wasting a lot of space. E.g. if space wasted > 16, may as
|
|
* well use another chunk. This may cause excessive calls to
|
|
* Cache_FreeHigh, so maybe only do it if wasting more than that
|
|
* (32/64/?).
|
|
*/
|
|
st_string_next = Hunk_TempAllocExtend(ST_STRING_CHUNK);
|
|
st_string_space = st_string_next ? ST_STRING_CHUNK : 0;
|
|
}
|
|
if (st_string_space >= length) {
|
|
ret = st_string_next;
|
|
st_string_next += length;
|
|
st_string_space -= length;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Insert string node "node" into rb_tree rooted at "root"
|
|
*/
|
|
qboolean
|
|
STree_Insert(struct stree_root *root, struct stree_node *node)
|
|
{
|
|
struct rb_node **p = &root->root.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
unsigned int len;
|
|
int cmp;
|
|
|
|
while (*p) {
|
|
parent = *p;
|
|
cmp = strcasecmp(node->string, stree_entry(parent)->string);
|
|
if (cmp < 0)
|
|
p = &(*p)->rb_left;
|
|
else if (cmp > 0)
|
|
p = &(*p)->rb_right;
|
|
else
|
|
return false; /* string already present */
|
|
}
|
|
root->entries++;
|
|
len = strlen(node->string);
|
|
if (len > root->maxlen)
|
|
root->maxlen = len;
|
|
if (len < root->minlen)
|
|
root->minlen = len;
|
|
rb_link_node(&node->node, parent, p);
|
|
rb_insert_color(&node->node, &root->root);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Insert string into rb tree, allocating the node dynamically.
|
|
* If alloc_str != 0, allocate and copy the string as well.
|
|
* NOTE: These allocations are only on the Temp hunk.
|
|
*/
|
|
qboolean
|
|
STree_InsertAlloc(struct stree_root *root, const char *s, qboolean alloc_str)
|
|
{
|
|
qboolean ret = false;
|
|
struct stree_node *n;
|
|
char *tmp;
|
|
|
|
n = STree_AllocNode();
|
|
if (n) {
|
|
if (alloc_str) {
|
|
tmp = STree_AllocString(strlen(s) + 1);
|
|
if (tmp) {
|
|
strcpy(tmp, s);
|
|
n->string = tmp;
|
|
}
|
|
} else {
|
|
n->string = s;
|
|
}
|
|
ret = STree_Insert(root, n);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
STree_Remove(struct stree_root *root, struct stree_node *node)
|
|
{
|
|
rb_erase(&node->node, &root->root);
|
|
}
|
|
|
|
/* STree_MaxMatch helper */
|
|
static int
|
|
ST_node_match(struct rb_node *n, const char *str, int min_match, int max_match)
|
|
{
|
|
if (n) {
|
|
max_match = ST_node_match(n->rb_left, str, min_match, max_match);
|
|
|
|
/* How much does this node match */
|
|
while (max_match > min_match) {
|
|
if (!strncasecmp(str, stree_entry(n)->string, max_match))
|
|
break;
|
|
max_match--;
|
|
}
|
|
max_match = ST_node_match(n->rb_right, str, min_match, max_match);
|
|
}
|
|
|
|
return max_match;
|
|
}
|
|
|
|
/*
|
|
* Given a prefix, return the maximum common prefix of all other strings in
|
|
* the tree which match the given prefix.
|
|
*/
|
|
char *
|
|
STree_MaxMatch(struct stree_root *root, const char *pfx)
|
|
{
|
|
int max_match, min_match, match;
|
|
struct rb_node *n;
|
|
struct stree_node *sn;
|
|
char *result = NULL;
|
|
|
|
/* Can't be more than the shortest string */
|
|
max_match = root->minlen;
|
|
min_match = strlen(pfx);
|
|
|
|
n = root->root.rb_node;
|
|
sn = stree_entry(n);
|
|
|
|
if (root->entries == 1) {
|
|
match = strlen(sn->string);
|
|
result = Z_Malloc(match + 2);
|
|
if (result) {
|
|
strncpy(result, sn->string, match);
|
|
result[match] = ' ';
|
|
result[match + 1] = 0;
|
|
}
|
|
} else if (root->entries > 1) {
|
|
match = ST_node_match(n, sn->string, min_match, max_match);
|
|
result = Z_Malloc(match + 1);
|
|
if (result) {
|
|
strncpy(result, sn->string, match);
|
|
result[match] = 0;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
struct stree_node *
|
|
STree_Find(struct stree_root *root, const char *s)
|
|
{
|
|
struct rb_node *p = root->root.rb_node;
|
|
struct stree_node *ret = NULL;
|
|
struct stree_node *node;
|
|
int cmp;
|
|
|
|
while (p) {
|
|
node = stree_entry(p);
|
|
cmp = strcasecmp(s, node->string);
|
|
if (cmp < 0)
|
|
p = p->rb_left;
|
|
else if (cmp > 0)
|
|
p = p->rb_right;
|
|
else {
|
|
ret = node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* An R-B Tree with n entries has a maximum height of 2log(n +1) */
|
|
static int
|
|
STree_MaxDepth(struct stree_root *root)
|
|
{
|
|
return 2 * Q_log2(root->entries + 1);
|
|
}
|
|
|
|
static void
|
|
STree_StackInit(struct stree_root *root)
|
|
{
|
|
root->stack = Z_Malloc(sizeof(struct stree_stack));
|
|
if (root->stack) {
|
|
struct stree_stack *s = root->stack;
|
|
s->depth = 0;
|
|
s->max_depth = STree_MaxDepth(root);
|
|
s->stack = Z_Malloc(s->max_depth * sizeof(struct rb_node *));
|
|
if (!s->stack) {
|
|
Z_Free(s);
|
|
root->stack = NULL;
|
|
}
|
|
}
|
|
/* Possibly this harsh failure is not suitable in all cases? */
|
|
if (!root->stack)
|
|
Sys_Error("%s: not enough mem for stack! (%i)", __func__,
|
|
STree_MaxDepth(root));
|
|
}
|
|
|
|
void
|
|
STree_ForEach_Init__(struct stree_root *root, struct stree_node **n)
|
|
{
|
|
/* Allocate the stack */
|
|
STree_StackInit(root);
|
|
|
|
/* Point to the first node */
|
|
if (root->root.rb_node)
|
|
*n = stree_entry(root->root.rb_node);
|
|
else
|
|
*n = NULL;
|
|
}
|
|
|
|
void
|
|
STree_ForEach_Cleanup__(struct stree_root *root)
|
|
{
|
|
if (root->stack) {
|
|
Z_Free(root->stack->stack);
|
|
Z_Free(root->stack);
|
|
root->stack = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
STree_StackPush(struct stree_root *root, struct rb_node *n)
|
|
{
|
|
struct stree_stack *s = root->stack;
|
|
assert(s->depth < s->max_depth);
|
|
s->stack[s->depth++] = n;
|
|
}
|
|
|
|
static struct rb_node *
|
|
STree_StackPop(struct stree_root *root)
|
|
{
|
|
struct stree_stack *s = root->stack;
|
|
assert(s->depth > 0);
|
|
return s->stack[--s->depth];
|
|
}
|
|
|
|
/*
|
|
* STree_WalkLeft - Helper for STree_ForEach
|
|
*
|
|
* Explanation of implied semantics:
|
|
* 1. If *n is not null, we haven't looked at it at all yet
|
|
* - Check the left child.
|
|
* - If child is non-null, push *n onto the stack, *n = (*n)->left; Goto 1.
|
|
* - If left child is null, keep this *n and exit (true)
|
|
* 2. If *n is null, we need to grab the node from the top of the stack
|
|
* - If the stack is empty, we're finished
|
|
* - Free the stack and exit (false)
|
|
* - Otherwise, *n = <pop the top of the stack> and exit (true)
|
|
*/
|
|
qboolean
|
|
STree_WalkLeft__(struct stree_root *root, struct stree_node **n)
|
|
{
|
|
struct rb_node *rb;
|
|
|
|
if (*n) {
|
|
rb = &(*n)->node;
|
|
while (rb->rb_left) {
|
|
STree_StackPush(root, rb);
|
|
rb = rb->rb_left;
|
|
}
|
|
*n = stree_entry(rb);
|
|
} else {
|
|
/* Null signifies that we need to pop from the stack */
|
|
if (root->stack->depth > 0) {
|
|
rb = STree_StackPop(root);
|
|
*n = stree_entry(rb);
|
|
} else
|
|
STree_ForEach_Cleanup__(root);
|
|
}
|
|
|
|
return *n != NULL;
|
|
}
|
|
|
|
/*
|
|
* STree_WalkRight__ - Helper for STree_ForEach
|
|
*
|
|
* Called at the end of a loop iteration. So, *n has been processed.
|
|
* - If *n has a right child, *n = right child. Exit.
|
|
* - Otherwise, *n = NULL (tells WalkLeft to grab parent next). Exit.
|
|
*/
|
|
void
|
|
STree_WalkRight__(struct stree_node **n)
|
|
{
|
|
struct rb_node *rb;
|
|
|
|
rb = &(*n)->node;
|
|
if (rb->rb_right)
|
|
*n = stree_entry(rb->rb_right);
|
|
else
|
|
*n = NULL;
|
|
}
|
|
|
|
/*
|
|
* Skip through the tree to a specified entry, without iterating
|
|
* through everything. This is basically STree_Find, but with stack
|
|
* pushes so the iteration can be continued. We then move n to the
|
|
* next node.
|
|
*/
|
|
void
|
|
STree_ForEach_After__(struct stree_root *root, struct stree_node **n,
|
|
const char *s)
|
|
{
|
|
struct rb_node *p;
|
|
struct stree_node *node;
|
|
int cmp;
|
|
|
|
*n = NULL;
|
|
p = root->root.rb_node;
|
|
while (p) {
|
|
node = stree_entry(p);
|
|
cmp = strcasecmp(s, node->string);
|
|
if (cmp < 0) {
|
|
STree_StackPush(root, p);
|
|
p = p->rb_left;
|
|
} else if (cmp > 0) {
|
|
p = p->rb_right;
|
|
} else {
|
|
*n = node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*n) {
|
|
/* found the exact node; skip on to the next one */
|
|
if (p->rb_right)
|
|
*n = stree_entry(p->rb_right);
|
|
else
|
|
*n = NULL;
|
|
} else {
|
|
/* Didn't find str. Don't walk the tree at all! */
|
|
root->stack->depth = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
STree_Completions(struct stree_root *out, struct stree_root *in, const char *s)
|
|
{
|
|
struct stree_node *n;
|
|
struct rb_node *rb = NULL;
|
|
int cmp, len;
|
|
|
|
len = strlen(s);
|
|
rb = in->root.rb_node;
|
|
|
|
/* Work our way down to the subtree required */
|
|
while (rb) {
|
|
n = stree_entry(rb);
|
|
cmp = strncasecmp(s, n->string, len);
|
|
if (cmp < 0)
|
|
rb = rb->rb_left;
|
|
if (cmp > 0)
|
|
rb = rb->rb_right;
|
|
else
|
|
break;
|
|
}
|
|
|
|
STree_StackInit(in);
|
|
|
|
while (rb) {
|
|
n = stree_entry(rb);
|
|
cmp = strncasecmp(s, n->string, len);
|
|
if (!cmp) {
|
|
STree_InsertAlloc(out, n->string, false);
|
|
if (rb->rb_left) {
|
|
if (rb->rb_right)
|
|
STree_StackPush(in, rb->rb_right);
|
|
rb = rb->rb_left;
|
|
} else {
|
|
rb = rb->rb_right;
|
|
}
|
|
} else if (cmp < 0) {
|
|
rb = rb->rb_left;
|
|
} else {
|
|
rb = rb->rb_right;
|
|
}
|
|
if (!rb && in->stack->depth > 0)
|
|
rb = STree_StackPop(in);
|
|
}
|
|
|
|
STree_ForEach_Cleanup__(in);
|
|
}
|