/*
 * alias.c Handles command aliases for irc.c 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990, 1995 Michael Sandroff and others 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#if 0
static	char	rcsid[] = "@(#)$Id: alias.c,v 1.25 1994/07/26 12:12:10 mrg Exp $";
#endif

#include "irc.h"
#include "alias.h"
#include "array.h"
#include "dcc.h"
#include "edit.h"
#include "files.h"
#include "history.h"
#include "hook.h"
#include "input.h"
#include "ircaux.h"
#include "names.h"
#include "output.h"
#include "parse.h"
#include "screen.h"
#include "server.h"
#include "status.h"
#include "vars.h"
#include "window.h"
#include "term.h"
#include "notify.h"
#include "options"
#include <sys/stat.h>

/* XXXX */
#ifndef MAXPATHLEN
#define MAXPATHLEN PATHSIZE
#endif

/* move this to alias.h! */
extern	int	parse_number 		_((char **));
static	char	*next_unit 		_((char *, char *, int *, int));
static	u_long	randm 			_((unsigned long));
static	void 	flush_variables 	_((void));
static	void 	flush_aliases2 		_((void));
Alias	*find_alias_with_args (Alias **, char *, int, int *, int, char *, int *);
static	char	*find_inline_with_args (char *, char *, int *);

static	char	
	*alias_detected 	_((void)), *alias_sent_nick 	_((void)), 
	*alias_recv_nick 	_((void)), *alias_msg_body 	_((void)),
	*alias_joined_nick 	_((void)), *alias_public_nick 	_((void)),
	*alias_dollar 		_((void)), *alias_channel 	_((void)),
	*alias_server 		_((void)), *alias_query_nick 	_((void)),
	*alias_target 		_((void)), *alias_nick 		_((void)),
	*alias_invite 		_((void)), *alias_cmdchar 	_((void)),
	*alias_line 		_((void)), *alias_away 		_((void)),
	*alias_oper 		_((void)), *alias_chanop 	_((void)),
	*alias_modes 		_((void)), *alias_buffer 	_((void)),
	*alias_time 		_((void)), *alias_version 	_((void)),
	*alias_currdir 		_((void)), *alias_current_numeric _((void)),
	*alias_server_version 	_((void)), *alias_show_userhost _((void)),
	*alias_show_realname 	_((void)), *alias_online 	_((void)),
	*alias_idle 		_((void)), *alias_version_str 	_((void));

typedef struct
{
	char	name;
	char	*(*func) _((void));
}	BuiltIns;

static	BuiltIns built_in[] =
{
	{ '.',		alias_sent_nick },
	{ ',',		alias_recv_nick },
	{ ':',		alias_joined_nick },
	{ ';',		alias_public_nick },
	{ '$',		alias_dollar },
	{ 'A',		alias_away },
	{ 'B',		alias_msg_body },
	{ 'C',		alias_channel },
	{ 'D',		alias_detected },
	{ 'E',		alias_idle },
	{ 'F',		alias_online },
/*	{ 'G' }, */
	{ 'H', 		alias_current_numeric },
	{ 'I',		alias_invite },
	{ 'J',		alias_version_str },
	{ 'K',		alias_cmdchar },
	{ 'L',		alias_line },
	{ 'M',		alias_modes },
	{ 'N',		alias_nick },
	{ 'O',		alias_oper },
	{ 'P',		alias_chanop },
	{ 'Q',		alias_query_nick },
	{ 'R',		alias_server_version },
	{ 'S',		alias_server },
	{ 'T',		alias_target },
	{ 'U',		alias_buffer },
	{ 'V',		alias_version },
	{ 'W',		alias_currdir },
	{ 'X',		alias_show_userhost },
	{ 'Y',		alias_show_realname },
	{ 'Z',		alias_time },
	{ 0,	 	NULL }
};

/* the 30 "standard" functions */
static	char	
	*function_channels 	_((char *)), 
	*function_connect 	_((char *)),
	*function_curpos 	_((char *)), 
	*function_decode 	_((unsigned char *)),
	*function_encode 	_((unsigned char *)),
	*function_index 	_((char *)), 
	*function_ischannel 	_((char *)),
	*function_ischanop 	_((char *)), 
	*function_left 		_((char *)),
	*function_listen 	_((char *)),
	*function_match 	_((char *)),
	*function_mid 		_((char *)),
	*function_pid 		_((char *)),
	*function_ppid 		_((char *)),
	*function_rand 		_((char *)),
	*function_right 	_((char *)),
	*function_rindex 	_((char *)),
	*function_rmatch 	_((char *)),
	*function_servers 	_((char *)),
	*function_srand 	_((char *)),
	*function_stime 	_((char *)),
	*function_strip 	_((char *)),
	*function_tdiff 	_((char *)),
	*function_tdiff2 	_((char *)),
	*function_time 		_((char *)),
	*function_tolower 	_((char *)),
	*function_toupper 	_((char *)),
	*function_userhost 	_((char *)),
	*function_winnum 	_((char *)),
	*function_winnam 	_((char *)),
	*function_word 		_((char *)),
	*function_utime		_((char *)),
	*function_strftime	_((char *)),

/* the 68 "extended" functions */
	*function_after 	_((char *)),
	*function_afterw 	_((char *)),
	*function_aliasctl	_((char *)),
	*function_ascii 	_((char *)),
	*function_before 	_((char *)),
	*function_beforew 	_((char *)),
	*function_center 	_((char *)),
	*function_channelmode	_((char *)),
	*function_chmod		_((char *)),
	*function_chngw 	_((char *)),
	*function_chops 	_((char *)),
	*function_chr 		_((char *)),
	*function_close 	_((char *)),
	*function_common 	_((char *)),
	*function_convert 	_((char *)),
	*function_copattern 	_((char *)),
	*function_crypt 	_((char *)),
	*function_diff 		_((char *)),
	*function_eof 		_((char *)),
	*function_epic		_((char *)),
	*function_fexist 	_((char *)),
	*function_filter 	_((char *)),
	*function_fromw 	_((char *)),
	*function_fsize	 	_((char *)),
	*function_geom		_((char *)),
	*function_glob		_((char *)),
	*function_idle		_((char *)),
	*function_info		_((char *)),
	*function_insertw 	_((char *)),
	*function_iptoname 	_((char *)),
	*function_isalpha 	_((char *)),
	*function_isdigit 	_((char *)),
	*function_jot 		_((char *)),
	*function_key 		_((char *)),
	*function_lastserver	_((char *)),
	*function_leftw 	_((char *)),
	*function_midw 		_((char *)),
	*function_mkdir		_((char *)),
	*function_nametoip 	_((char *)),
	*function_nochops 	_((char *)),
	*function_notify	_((char *)),
	*function_notw 		_((char *)),
	*function_numonchannel 	_((char *)),
	*function_numsort	_((char *)),
	*function_numwords 	_((char *)),
	*function_onchannel 	_((char *)),
	*function_open 		_((char *)),
	*function_pattern 	_((char *)),
	*function_pass		_((char *)),
	*function_read 		_((char *)),
	*function_remw 		_((char *)),
	*function_rename 	_((char *)),
	*function_repeat	_((char *)),
	*function_restw 	_((char *)),
	*function_reverse 	_((char *)),
	*function_revw 		_((char *)),
	*function_rfilter 	_((char *)),
	*function_rightw 	_((char *)),
	*function_rmdir 	_((char *)),
	*function_rpattern 	_((char *)),
	*function_sar 		_((char *)),
	*function_server_version _((char *)),
	*function_servername	_((char *)),
	*function_sort		_((char *)),
	*function_split 	_((char *)),
	*function_splice 	_((char *)),
	*function_stripansi	_((char *)),
	*function_strlen	_((char *)),
	*function_tow 		_((char *)),
	*function_translate 	_((char *)),
	*function_truncate 	_((char *)),
	*function_twiddle	_((char *)),
	*function_umask		_((char *)),
	*function_umode		_((char *)),
	*function_uniq		_((char *)),
	*function_unlink 	_((char *)),
	*function_which 	_((char *)),
	*function_winserv	_((char *)),
	*function_winsize	_((char *)),
	*function_winvisible	_((char *)),
	*function_write 	_((char *)),
	*function_writeb	_((char *));


typedef char *(bf) _((char *));
typedef struct
{
	const char	*name;
	bf 		*func;
}	BuiltInFunctions;


/* 
 * This is the built-in function list.  This list *must* be sorted because
 * it is binary searched.   See the code for each function to see how it
 * is used.  Or see the help files.  Or see both.  Or look at the code
 * and see how it REALLY works, irregardless of the documentation >;-)
 */
static BuiltInFunctions	built_in_functions[] =
{
	{ "AFTER",              function_after 		},
	{ "AFTERW",             function_afterw 	},
	{ "ALIASCTL",		function_aliasctl	},
	{ "ASCII",              function_ascii 		},
	{ "BEFORE",             function_before 	},
	{ "BEFOREW",            function_beforew 	},
	{ "CENTER",		function_center 	},
	{ "CHANMODE",		function_channelmode	},
	{ "CHANUSERS",		function_onchannel 	},
	{ "CHMOD",		function_chmod		},
	{ "CHNGW",              function_chngw 		},
	{ "CHOPS",              function_chops 		},
	{ "CHR",                function_chr 		},
	{ "CLOSE",		function_close 		},
	{ "COMMON",             function_common 	},
	{ "CONNECT",		function_connect 	},
	{ "CONVERT",		function_convert 	},
	{ "COPATTERN",          function_copattern 	},
	{ "CRYPT",		function_crypt		},
	{ "CURPOS",		function_curpos 	},
	{ "DECODE",	  (bf *)function_decode 	},
	{ "DELARRAY",           function_delarray 	},
	{ "DELITEM",            function_delitem 	},
	{ "DIFF",               function_diff 		},
	{ "ENCODE",	  (bf *)function_encode 	},
	{ "EOF",		function_eof 		},
	{ "EPIC",		function_epic		},
	{ "FEXIST",             function_fexist 	},
	{ "FILTER",             function_filter 	},
	{ "FINDITEM",           function_finditem 	},
	{ "FROMW",              function_fromw 		},
	{ "FSIZE",		function_fsize		},
	{ "GEOM",		function_geom		},
	{ "GETARRAYS",          function_getarrays 	},
	{ "GETITEM",            function_getitem 	},
	{ "GETMATCHES",         function_getmatches 	},
	{ "GETRMATCHES",        function_getrmatches 	},
	{ "GLOB",		function_glob		},
	{ "IDLE",		function_idle		},
	{ "IFINDFIRST",         function_ifindfirst 	},
	{ "IFINDITEM",          function_ifinditem 	},
	{ "IGETITEM",           function_igetitem 	},
	{ "INDEX",		function_index 		},
	{ "INDEXTOITEM",        function_indextoitem 	},
	{ "INFO",		function_info		},
	{ "INSERTW",            function_insertw 	},
	{ "IPTONAME",		function_iptoname 	},
	{ "ISALPHA",		function_isalpha 	},
	{ "ISCHANNEL",		function_ischannel 	},
	{ "ISCHANOP",		function_ischanop 	},
	{ "ISDIGIT",		function_isdigit 	},
	{ "ITEMTOINDEX",        function_itemtoindex 	},
	{ "JOT",                function_jot 		},
	{ "KEY",                function_key 		},
	{ "LASTSERVER",		function_lastserver	},
	{ "LEFT",		function_left 		},
	{ "LEFTW",              function_leftw 		},
	{ "LISTEN",		function_listen 	},
	{ "MATCH",		function_match 		},
	{ "MATCHITEM",          function_matchitem 	},
	{ "MID",		function_mid 		},
	{ "MIDW",               function_midw 		},
	{ "MKDIR",		function_mkdir		},
	{ "MYCHANNELS",		function_channels 	},
	{ "MYSERVERS",		function_servers 	},
	{ "NAMETOIP",		function_nametoip 	},
	{ "NOCHOPS",            function_nochops 	},
	{ "NOTIFY",		function_notify		},
	{ "NOTW",               function_notw 		},
	{ "NUMARRAYS",          function_numarrays 	},
	{ "NUMITEMS",           function_numitems 	},
	{ "NUMONCHANNEL",	function_numonchannel 	},
	{ "NUMSORT",		function_numsort	},
	{ "NUMWORDS",		function_numwords	},
	{ "ONCHANNEL",          function_onchannel 	},
	{ "OPEN",		function_open 		},
	{ "PASS",		function_pass		},
	{ "PATTERN",            function_pattern 	},
	{ "PID",		function_pid 		},
	{ "POP",		function_pop 		},
	{ "PPID",		function_ppid 		},
	{ "PUSH",		function_push 		},
	{ "RAND",		function_rand 		},
	{ "READ",		function_read 		},
	{ "REMW",               function_remw 		},
	{ "RENAME",		function_rename 	},
	{ "REPEAT",		function_repeat		},
	{ "RESTW",              function_restw 		},
	{ "REVERSE",            function_reverse 	},
	{ "REVW",               function_revw 		},
	{ "RFILTER",            function_rfilter 	},
	{ "RIGHT",		function_right 		},
	{ "RIGHTW",             function_rightw 	},
	{ "RINDEX",		function_rindex 	},
	{ "RMATCH",		function_rmatch 	},
	{ "RMATCHITEM",         function_rmatchitem 	},
	{ "RMDIR",		function_rmdir 		},
	{ "RPATTERN",           function_rpattern 	},
	{ "SAR",		function_sar 		},
	{ "SERVERNAME",		function_servername	},
	{ "SETITEM",            function_setitem 	},
	{ "SHIFT",		function_shift 		},
	{ "SORT",		function_sort		},
	{ "SPLICE",		function_splice 	},
	{ "SPLIT",		function_split 		},
	{ "SRAND",		function_srand 		},
	{ "STIME",		function_stime 		},
	{ "STRFTIME",		function_strftime	},
	{ "STRIP",		function_strip 		},
	{ "STRIPANSI",		function_stripansi	},
	{ "STRLEN",		function_strlen		},
	{ "TDIFF",		function_tdiff 		},
	{ "TDIFF2",		function_tdiff2 	},
	{ "TIME",		function_time 		},
	{ "TOLOWER",		function_tolower 	},
	{ "TOUPPER",		function_toupper 	},
	{ "TOW",                function_tow 		},
	{ "TR",			function_translate 	},
	{ "TRUNC",		function_truncate 	},
	{ "TWIDDLE",		function_twiddle	},
	{ "UMASK",		function_umask		},
	{ "UNIQ",		function_uniq		},
	{ "UNLINK",		function_unlink 	},
	{ "UNSHIFT",		function_unshift 	},
	{ "USERHOST",		function_userhost 	},
	{ "USERMODE",		function_umode		},
	{ "UTIME",		function_utime	 	},
	{ "VERSION",		function_server_version },
	{ "WHICH",		function_which 		},
	{ "WINNAM",		function_winnam 	},
	{ "WINNUM",		function_winnum 	},
	{ "WINSERV",		function_winserv	},
	{ "WINSIZE",		function_winsize	},
	{ "WINVISIBLE",		function_winvisible	},
	{ "WORD",		function_word 		},
	{ "WRITE",		function_write 		},
	{ "WRITEB",		function_writeb		},
	{ (char *) 0,		NULL }
};

#define	NUMBER_OF_ALIASES (sizeof(built_in_functions) / sizeof(BuiltInFunctions)) - 2

/* alias_illegals: characters that are illegal in alias names */
	char	alias_illegals[] = " #+-*/\\()={}[]<>!@$%^~`,?;:|'\"";

Alias	*alias_list[] =
{
	(Alias *) 0,
	(Alias *) 0
};

/* alias_string: the thing that gets replaced by the $"..." construct */
static	char	*alias_string = (char *) 0;

/* function_stack and function_stkptr - hold the return values from functions */
static	char	*function_stack[128] = { (char *) 0 };
static	int	function_stkptr = 0;


/* makes foo[one][two] look like tmp.one.two -- got it? */
#ifdef __STDC__
extern char *remove_brackets (char *name, char *args, int *arg_flag)
#else
extern char *remove_brackets (name, args, arg_flag)
char *name;
char *args;
int *arg_flag;
#endif
{
	char *ptr, *right, *result1, *rptr, *retval;

	/* XXXX - ugh. */
	rptr = m_strdup(name);

	while ((ptr = strchr(rptr, '[')))
	{
		*ptr++ = 0;
		right = ptr;
		if ((ptr = MatchingBracket(right, '[', ']')))
			*ptr++ = 0;

		result1 = upper(expand_alias(NULL, right, args, arg_flag, NULL));
		retval = (char *)new_malloc(strlen(rptr) + strlen(result1) + (ptr ? strlen(ptr) : 0) + 2);
		strcpy(retval, rptr);
		strcat(retval, ".");
		strcat(retval, result1);
		if (ptr)
			strcat(retval, ptr);

		new_free(&result1);
		if (rptr)
			new_free(&rptr);
		rptr = retval;
	}
	return rptr;
}

/*
 * find_alias: looks up name in in alias list.  Returns the Alias entry if
 * found, or null if not found.   If unlink is set, the found entry is
 * removed from the list as well.  If match is null, only perfect matches
 * will return anything.  Otherwise, the number of matches will be returned. 
 */
#ifdef __STDC__
extern Alias *find_alias (Alias **list, char *name, int unlink, int *match, int unstub)
#else
extern	Alias	*find_alias (list, name, unlink, match, unstub)
	Alias	**list;
	char	*name;
	int	unlink,	*match,	unstub;
#endif
{
	int af;
	return find_alias_with_args(list, name, unlink, match, unstub, NULL, &af);
}

#ifdef __STDC__
extern	Alias	*find_alias_with_args (Alias **list, char *name, int unlink, int *match, int unstub, char *args, int *args_flag)
#else
extern	Alias	*find_alias_with_args (list, name, unlink, match, unstub, args, args_flag)
	Alias	**list;
	char	*name;
	int	unlink;
	int	*match;
	int	unstub;
	char 	*args;
	int 	*args_flag;
#endif
{
	Alias	*tmp,
		*last = (Alias *) 0;
	int	cmp,
		len;
	int (*cmp_func) ();

	if (match)
	{
		*match = 0;
		cmp_func = strncmp;
	}
	else
		cmp_func = strcmp;

	if (name)
	{
		/* 
		 * The function that is looking for the variable should not
		 * be responsible for resolving any array variables.  That
		 * should be and is our job.  So anyone can pass us a
		 * variable name like 'foo[$one][two]' and we know just
		 * what to do with it.  righto?  
		 */
		name = upper(remove_brackets(name, args, args_flag));
		len = strlen(name);
		for (tmp = *list; tmp; tmp = tmp->next)
		{
			if ((cmp = cmp_func(name, tmp->name, len)) == 0)
			{
				static already_looking = 0;

				if (tmp->stub && unstub)
				{
					/* If already_looking is 1, then
					 * we are un-stubbing this alias
					 * because we are loading a file
					 * that presumably it must be in.
					 * So we dont load it again (duh).
					 */
					if (!already_looking)
					{
						already_looking = 1;
						load("LOAD", tmp->stub, empty_string);
						already_looking = 0;
					}
					/* it is very important for every
					 * caller of this function to assume
					 * that 'stub' is always NULL unless
					 * they are checking for non-NULLness.
					 * NO NEED to ever free or reassign
					 * the stub field anywhere but here!
					 */
					/* Whee.. recursion implies that
					 * the interesting parts of tmp have
					 * already been re-assigned by the 
					 * load, so we just clean up after
					 * ourselves.  We have to complete
					 * this call, becuase whoever called
					 * us is expecting a reply, so the load
					 * was just a temporary time-out while
					 * we feteched the interesting alias.
					 */
					new_free(&(tmp->stub));

					/* I dont know if this will get
					   me in trouble or not, but im 
					   going to try it.  If we have
					   loaded the file, and this alias
					   is therefore de-stubbed, if
					   it wasnt assigned somewhere in
					   the file, then we should unlink
					   the stub. */
					if (!tmp->stuff)
						unlink = 1;
				}

				if (unlink)
				{
					if (last)
						last->next = tmp->next;
					else
						*list = tmp->next;
				}
				if (match)
				{
					(*match)++;
					if (strlen(tmp->name) == len)
					{
						*match = 0;
						new_free(&name);
						return (tmp);
					}
				}
				else
				{
					new_free(&name);
					return (tmp);
				}
			}
			else if (cmp < 0)
				break;
			last = tmp;
		}
	}

	new_free(&name);

	if (match && (*match == 1))
		return (last);
	else
		return ((Alias *) 0);
}

/*
 * insert_alias: adds the given alias to the alias list.  The alias list is
 * alphabetized by name 
 */
#ifdef __STDC__
extern	void	insert_alias (Alias **list, Alias *alias)
#else
extern	void	insert_alias (list, alias)
	Alias	**list;
	Alias	*alias;
#endif
{
	Alias	*tmp,
		*last,
		*foo;

	last = (Alias *) 0;
	for (tmp = *list; tmp; tmp = tmp->next)
	{
		if (strcmp(alias->name, tmp->name) < 0)
			break;
		last = tmp;
	}
	if (last)
	{
		foo = last->next;
		last->next = alias;
		alias->next = foo;
	}
	else
	{
		alias->next = *list;
		*list = alias;
	}
}

/*
 * add_alias: given the alias name and "stuff" that makes up the alias,
 * add_alias() first checks to see if the alias name is already in use... if
 * so, the old alias is replaced with the new one.  If the alias is not
 * already in use, it is added. 
 */
#ifdef __STDC__
extern void	add_alias (int type, char *name, char *stuff)
#else
extern void	add_alias (type, name, stuff)
	int	type;
	char	*name,
		*stuff;
#endif
{
	Alias	*tmp;
	char	*ptr;
	int	af;

	upper(name);

	/* 
	 * Looks around -- Hmmm. noone's looking... Ill sneak this in
	 * here and hope noone notices. >;-)   This fixes a whole world
	 * of problems...  Of course we have to make sure we dont
	 * do this for FUNCTION_RETURN. *sigh*
	 */
	if ((!stuff || !*stuff) && strcmp(name, "FUNCTION_RETURN"))
	{
		delete_alias(type, name);
		return;
	}

	name = remove_brackets(name, NULL, &af);

	if (type == VAR_ALIAS && (ptr = sindex(name, alias_illegals)) != NULL)
	{
		yell("ASSIGN names may not contain '%c'", *ptr);
		new_free(&name);
		return;
	}

	/* FUNCTION_RETURN must die die die. */
	if (type == VAR_ALIAS && !strcmp(name, "FUNCTION_RETURN"))
	{
		if (function_stack[function_stkptr])
			new_free(&function_stack[function_stkptr]);
		malloc_strcpy(&function_stack[function_stkptr], stuff);
		new_free(&name);
		return;
	}

	if (type == COMMAND_ALIAS)
		say("Alias	%s added", name);
	else
		say("Assign %s added", name);

	/* We dont need to unstub an alias if we're just going to add a new one. */
	if ((tmp = find_alias(&(alias_list[type]), name, 1, (int *) 0, 0)) == (Alias *) 0)
	{
		tmp = (Alias *) new_malloc(sizeof(Alias));
		tmp->name = (char *) 0;
		tmp->stuff = (char *) 0;
		tmp->stub = (char *) 0;
	}
	malloc_strcpy(&(tmp->name), name);
	malloc_strcpy(&(tmp->stuff), stuff);
	new_free(&tmp->stub);
	tmp->mark = 0;
	tmp->global = loading_global;
	insert_alias(&(alias_list[type]), tmp);
	new_free(&name);
}

/*
 * add_stub: You give it an alias name and a filename, and it generates
 * a stub for that alias.  If an alias is already in place for that name,
 * it is duly clobbered.  If it isnt already in place, this makes a new one.
 */
#ifdef __STDC__
static void	add_stub (int type, char *name, char *file)
#else
static void	add_stub (type, name, file)
	int	type;
	char	*name,
		*file;
#endif
{
	Alias	*tmp;
	char	*ptr;

	upper(name);
	if (type == COMMAND_ALIAS)
		say("Alias	%s stubbed to file %s", name, file);
	else
	{
		if (!strcmp(name, "FUNCTION_RETURN"))
		{
			yell("You cannot stub the FUNCTION_RETURN variable");
			return;
		}
		if ((ptr = sindex(name, alias_illegals)) != NULL)
		{
			yell("Assign names may not contain '%c'", *ptr);
			return;
		}
		say("Assign	%s stubbed to file %s", name, file);
	}
	if ((tmp = find_alias(&(alias_list[type]), name, 1, (int *) 0, 0)) == (Alias *) 0)
	{
		tmp = (Alias *)new_malloc(sizeof(Alias));
		tmp->name = (char *) 0;
		tmp->stuff = (char *) 0;
		tmp->stub = (char *) 0;
	}
	malloc_strcpy(&(tmp->name), name);
	malloc_strcpy(&(tmp->stub), file);
	new_free(&(tmp->stuff));
	tmp->stuff = NULL;
	tmp->mark = 0;
	tmp->global = loading_global;
	insert_alias(&(alias_list[type]), tmp);
}

#ifdef __STDC__
static	char	*built_in_alias (char c)
#else
static	char	*built_in_alias (c)
	char	c;
#endif
{
	BuiltIns	*tmp;

	for (tmp = built_in;tmp->name;tmp++)
		if (c == tmp->name)
			return tmp->func();

	return m_strdup(empty_string);
}

/*
 * find_inline: This simply looks up the given str.  It first checks to see
 * if its a user variable and returns it if so.  If not, it checks to see if
 * it's an IRC variable and returns it if so.  If not, it checks to see if
 * its and environment variable and returns it if so.  If not, it returns
 * null.  It mallocs the returned string 
 */
#ifdef __STDC__
static char *find_inline(char *str)
#else
static char *find_inline (str)
char *str;
#endif
{
	int af;
	return find_inline_with_args(str, NULL, &af);
}

#ifdef __STDC__
static	char	*find_inline_with_args (char *str, char *args, int *args_flag)
#else
static	char	*find_inline_with_args (str, args, args_flag)
	char	*str, *args;
	int *args_flag;
#endif
{
	Alias	*alias;
	char	*ret = NULL;

	if ((alias = find_alias_with_args(&(alias_list[VAR_ALIAS]), str, 0, NULL, 1, args, args_flag)) != NULL)
		ret = alias->stuff;
	else if ((strlen(str) == 1) && (ret = built_in_alias(*str)))
		return ret;
	else if ((ret = make_string_var(str)))
		;
	else
		ret = getenv(str);

	return m_strdup(ret);
}

#ifdef __STDC__
static	char	*call_function (char *name, char *f_args, char *args, int *args_flag)
#else
static	char	*call_function (name, f_args, args, args_flag)
	char	*name,
		*f_args,
		*args;
	int	*args_flag;
#endif
{
	char	*tmp;
	char	*result = (char *) 0;
	char	*debug_copy = (char *) 0;
	int min, max, pos, old_pos = -1, c;

	tmp = expand_alias((char *) 0, f_args, args, args_flag, NULL);
	if (get_int_var(DEBUG_VAR) & DEBUG_FUNCTIONS)
		malloc_strcpy(&debug_copy, tmp);

	upper(name);
	min = 0;
	max = NUMBER_OF_ALIASES + 1;
	while (1)
	{
		pos = (max + min) / 2;
		if (pos == old_pos)
		{
			pos = -1;
			break;
		}
		old_pos = pos;
		c = strcmp(name, built_in_functions[pos].name);
		if (c == 0)
			break;
		else if (c > 0)
			min = pos;
		else
			max = pos;
	}

	if (pos != -1)
		result = built_in_functions[pos].func(tmp);
	else
	{
		int size = strlen(name) + strlen(tmp) + 2;
#ifdef __GNUC__
		char sub_buffer[size];
#else
		char *sub_buffer = (char *)new_malloc(size);
#endif
		strcpy(sub_buffer, name);
		strcat(sub_buffer, space);
		strcat(sub_buffer, tmp);

		function_stkptr++;
		function_stack[function_stkptr] = NULL;
		parse_command(sub_buffer, 0, empty_string);
		result = function_stack[function_stkptr];
		function_stack[function_stkptr] = NULL;
		if (!result)
			malloc_strcpy(&result, empty_string);
		function_stkptr--;
#ifndef __GNUC__
		new_free(&sub_buffer);
#endif
	}
	if (debug_copy)
	{
		yell("Function %s(%s) returned %s", name, debug_copy, result);
		new_free(&debug_copy);
	}
	new_free(&tmp);
	return result;
}


/* canon_number: canonicalizes number to something relevant */
/* If FLOATING_POINT_MATH isnt set, it truncates it to an integer */
#ifdef __STDC__
static char *canon_number (char *input)
#else
static char *canon_number (input)
char *input;
#endif
{
	int end = strlen(input);

	if (end)
		end--;
	else
		return input;		/* nothing to do */

	if (get_int_var(FLOATING_POINT_MATH_VAR))
	{
		/* remove any trailing zeros */
		while (input[end] == '0')
			end--;

		/* If we removed all the zeros and all that is
		   left is the decimal point, remove it too */
		if (input[end] == '.')
			end--;

		input[end+1] = 0;
	}
	else
	{
		char *dot = index(input, '.');
		if (dot)
			*dot = 0;
	}

	return input;
}


/* Given a pointer to an operator, find the last operator in the string */
#ifdef __STDC__
static char	*lastop (char *ptr)
#else
static char	*lastop (ptr)
	char	*ptr;
#endif
{
	/* dont ask why i put the space in there. */
	while (ptr[1] && index("!=<>&^|#+/%,-* ", ptr[1]))
		ptr++;
	return ptr;
}




#define	NU_EXPR	0
#define	NU_ASSN	1
#define NU_TERT 2
#define NU_CONJ 3
#define NU_BITW 4 
#define NU_COMP 5 
#define NU_ADD  6 
#define NU_MULT 7 
#define NU_UNIT 8

/*
 * Cleaned up/documented by Jeremy Nelson, Feb 1996.
 *
 * What types of operators does ircII (EPIC) support?
 *
 * The client handles as 'special cases" the () and []
 * operators which have ircII-specific meanings.
 *
 * The general-purpose operators are:
 *
 * additive class:  		+, -, ++, --, +=, -=
 * multiplicative class: 	*, /, %, *=, /=, %=
 * string class: 		##, #=
 * and-class:			&, &&, &=
 * or-class:			|, ||, |=
 * xor-class:			^, ^^, ^=
 * equality-class:		==, !=
 * relative-class:		<, <=, >, >=
 * negation-class:		~ (1s comp), ! (2s comp)
 * selector-class:		?: (tertiary operator)
 * list-class:			, (comma)
 *
 * Backslash quotes any character not within [] or (),
 * which have their own quoting rules (*sigh*)
 */ 
#ifdef __STDC__
static	char	*next_unit (char *str, char *args, int *arg_flag, int stage)
#else
static	char	*next_unit (str, args, arg_flag, stage)
	char	*str,			/* start of token */
		*args;			/* the values of $0, $1, etc */
	int	*arg_flag,		/* whether we use $0, $1, etc */
		stage;			/* What parsing level were at */
#endif
{
	char	*ptr,			/* pointer to the current op */
		*ptr2,			/* used to point matching brackets */
		*right,			/* used to denote end of bracket-set */
		*lastc,			/* used to denote end of token-set */
#define TMPSIZ 511
		tmp[TMPSIZ + 1],	/* place to sprintf() into */
		op;			/* the op were working on */
	int	got_sloshed = 0,	/* If the last char was a slash */
		display;

	char	*result1 = (char *) 0,	/* raw lefthand-side of operator */
		*result2 = (char *) 0,	/* raw righthand-side of operator */
		*varname = (char *) 0;	/* Where we store varnames */
	long	value1 = 0,		/* integer value of lhs */
		value2 = 0,		/* integer value of rhs */
		value3 = 0;		/* integer value of operation */
	double	dvalue1 = 0.0,		/* floating value of lhs */
		dvalue2 = 0.0,		/* floating value of rhs */
		dvalue3 = 0.0;		/* floating value of operation */


/*
 * These macros make my life so much easier, its not funny.
 */

/*
 * An implied operation is one where the left-hand argument is
 * "implied" because it is both an lvalue and an rvalue.  We use
 * the rvalue of the left argument when we do the actual operation
 * then we do an assignment to the lvalue of the left argument.
 */
#define SETUP_IMPLIED(var1, var2, func)					\
{									\
	char *result1 = NULL,						\
	     *result2 = NULL;						\
									\
	/* Save the type of op, skip over the X=. */			\
	op = *ptr;							\
	*ptr++ = '\0';							\
	ptr++;								\
									\
	/* Figure out the variable name were working on */		\
	varname = expand_alias(NULL, str, args, arg_flag, NULL);	\
	lastc = varname + strlen(varname) - 1;				\
	while (lastc > varname && *lastc == ' ')			\
		*lastc-- = '\0';					\
	while (my_isspace(*varname))					\
		 varname++;						\
									\
	/* Get the value of the implied argument */			\
	result1 = find_inline(varname);					\
	var1 = (result1 && *result1) ? func (result1) : 0;		\
	new_free(&result1);						\
									\
	/* Get the value of the explicit argument */			\
	result2 = next_unit(ptr, args, arg_flag, stage);		\
	var2 = func (result2);						\
	new_free(&result2);						\
}

/*
 * This make sure the calculated value gets back into the lvalue of
 * the left operator, and turns the display back on.
 */
#define CLEANUP_IMPLIED()						\
									\
	/* Make sure were really an implied case */			\
	if (ptr[-1] == '=' && stage == NU_ASSN)				\
	{								\
		/* Turn off the display */				\
		int display = window_display;				\
		window_display = 0;					\
									\
		/* Make sure theres an lvalue */			\
		if (*varname)						\
			add_alias(VAR_ALIAS, varname, tmp);		\
		else							\
			yell("Invalid assignment: No lvalue");		\
									\
		/* Turn the display back on */				\
		window_display = display;				\
	}


/*
 * This sets up an ordinary explicit binary operation, where both
 * arguments are used as rvalues.  We just recurse and get their
 * values and then do the operation on them.
 */
#define SETUP_BINARY(var1, var2, func)					\
{									\
	char *result1 = NULL, *result2 = NULL;				\
									\
	/* Save the op were working on cause we clobber it. */		\
	op = *ptr;							\
	*ptr++ = '\0';							\
									\
	/* Get the two explicit operands */				\
	result1 = next_unit(str, args, arg_flag, stage);		\
	result2 = next_unit(ptr, args, arg_flag, stage);		\
									\
	/* Convert them with the specified function */			\
	var1 = func (result1);						\
	var2 = func (result2);						\
									\
	/* Clean up the mess we created. */				\
	new_free(&result1);						\
	new_free(&result2);						\
}


/*
 * This sets up a type-independant section of code for doing an
 * operation when the X and X= forms are both valid.
 */
#define SETUP(var1, var2, func, STAGE)					\
{									\
	/* If its an X= op, do an implied operation */			\
	if (ptr[1] == '=' && stage == NU_ASSN)				\
		SETUP_IMPLIED(var1, var2, func)				\
									\
	/* Else if its just an X op, do a binary operation */		\
	else if (ptr[1] != '=' && stage == STAGE)			\
		SETUP_BINARY(var1, var2, func)				\
									\
	/* Its not our turn to do this operation, just punt. */		\
	else								\
	{								\
		ptr = lastop(ptr);					\
		break;							\
	}								\
}

/* This does a setup for a floating-point operation. */
#define SETUP_FLOAT_OPERATION(STAGE)					\
	SETUP(dvalue1, dvalue2, atof, STAGE)

/* This does a setup for an integer operation. */
#define SETUP_INTEGER_OPERATION(STAGE)					\
	SETUP(value1, value2, atol, STAGE)

	/* remove leading spaces */
	while (my_isspace(*str))
		++str;

	/* If there's nothing there, return it */
	if (!*str)
		return m_strdup(empty_string);


	/* find the end of the rest of the expression */
	if ((lastc = str+strlen(str)) > str)
		lastc--;

	/* and remove trailing spaces */
	while (my_isspace(*lastc))
		*lastc-- = '\0';

	/* 
	 * If we're in the last parsing level, and this token is in parens,
	 * strip the parens and parse the insides immediately.
	 */
	if (stage == NU_UNIT && *lastc == ')' && *str == '(')
	{
		str++, *lastc-- = '\0';
		return next_unit(str, args, arg_flag, NU_EXPR);
	}


	/* 
	 * Ok. now lets get some work done.
	 * 
	 * Starting at the beginning of the string, look for something
	 * resembling an operator.  This divides the expression into two
	 * parts, a lhs and an rhs.  The rhs starts at "str", and the
	 * operator is at "ptr".  So if you do ptr = lastop(ptr), youll
	 * end up at the beginning of the rhs, which is the rest of the
	 * expression.  You can then parse it at the same level as the
	 * current level and it will recursively trickle back an rvalue
	 * which you can then apply to the lvalue give the operator.
	 *
	 * PROS: This is a very simplistic setup and not (terribly) confusing.
	 * CONS: Every operator is evaluated right-to-left which is *WRONG*.
	 */

	for (ptr = str; *ptr; ptr++)
	{
		if (got_sloshed)
		{
			got_sloshed = 0;
			continue;
		}

		switch(*ptr)
		{
		case '\\':
		{
			got_sloshed = 1;
			continue;
		}

		/*
		 * Parentheses have two contexts:  
		 * 1) (text) is a unary precedence operator.  It is nonassoc,
		 *	and simply parses the insides immediately.
		 * 2) text(text) is the function operator.  It calls the
		 *	specified function/alias passing it the given args.
		 */
		case '(':
		{
			/*
			 * If we're not in NU_UNIT, then we have a paren-set
			 * that (probably) is still an left-operand for some
			 * binary op.  Anyhow, we just immediately parse the
			 * paren-set, as thats the general idea of parens.
			 */
			if (stage != NU_UNIT || ptr == str)
			{
				/* 
				 * If there is no matching ), gobble up the
				 * entire expression.
				 */
				if (!(ptr2 = MatchingBracket(ptr+1, LEFT_PAREN, RIGHT_PAREN)))
					ptr = ptr + strlen(ptr) - 1;
				else
					ptr = ptr2;
				break;
			}

			/* Ok, now 'ptr' points at the end of the paren-set */
			*ptr++ = '\0';

			/*
			 * At this point, 'ptr' points at either the beginning
			 * of the token or just after the closing paren of the
			 * paren-set we just parsed, so 'right' points at the
			 * next token.
			 */
			right = ptr;

			/*
			 * and if that token is a left-paren, then we have a
			 * function call.  We gobble up the arguments and
			 * ship it all off to call_function.
			 */
			if ((ptr = MatchingBracket(right, LEFT_PAREN, RIGHT_PAREN)))
				*ptr++ = '\0';

			result1 = call_function(str, right, args, arg_flag);

			/*
			 * and what do we do with this value?  Why we prepend
			 * it to the next token!  This is actually a hack
			 * that if you have a NON-operator as the next token,
			 * it has an interesting side effect:
			 * ie:
			 * 	/eval echo ${foobar()4 + 3}
			 * where
			 *	alias foobar {@ function_return = 2}
			 *
			 * you get '27' as a return value, "as-if" you had done
			 *
			 *	/eval echo ${foobar() ## 4 + 3}
			 *
			 * Dont depend on this behavior.
			 */
			if (ptr && *ptr)
			{
				malloc_strcat(&result1, ptr);
				result2 = next_unit(result1, args, arg_flag, stage);
				new_free(&result1);
				result1 = result2;
			}

			return result1;
		}

		/*
		 * Brackets have two contexts:
		 * [text] is the literal-text operator.  The contents are
		 * 	not parsed as an lvalue, but as literal text.
		 *      This also covers the case of the array operator,
		 *      since it just appends whats in the [] set with what
		 *      came before it.
		 *
		 * The literal text operator applies not only to entire
		 * tokens, but also to the insides of array qualifiers.
		 */
		case '[':
		{
			if (stage != NU_UNIT)
			{
				if (!(ptr2 = MatchingBracket(ptr+1, LEFT_BRACKET, RIGHT_BRACKET)))
					ptr = ptr+strlen(ptr)-1;
				else
					ptr = ptr2;
				break;
			}

			/* ptr points right after the [] set */
			*ptr++ = '\0';
			right = ptr;

			/*
			 * At this point, we check to see if it really is a
			 * '[', and if it is, we skip over it.
			 */
			if ((ptr = MatchingBracket(right, LEFT_BRACKET, RIGHT_BRACKET)))
				*ptr++ = '\0';

			/* 
			 * Here we expand what is inside the [] set, as
			 * literal text.
			 */
			result1 = expand_alias(NULL, right, args, arg_flag, NULL);

			/*
			 * You need to look closely at this, as this test 
			 * is actually testing to see if (ptr != str) at the
			 * top of this case, which would imply that the []
			 * set was an array qualifier to some other variable.
			 *
			 * Before you ask "how do you know that?"  Remember
			 * that if (ptr == str) at the beginning of the case,
			 * then when we  *ptr++ = 0, we would then be setting
			 * *str to 0; so testing to see if *str is not zero
			 * tells us if (ptr == str) was true or not...
			 */
			if (*str)
			{
				int size = strlen(str) + (result1 ? strlen(result1) : 0) + (ptr ? strlen(ptr) : 0) + 2;
#ifdef __GNUC__
				/* array qualifier */
				char result2[size];
#else
				char *result2 = (char *)new_malloc(size);
#endif

				strcpy(result2, str);
				strcat(result2, ".");
				strcat(result2, result1);

				new_free(&result1);

				/*
				 * Notice of unexpected behavior:
				 *
				 * If $foobar.onetwo is "999"
				 * then ${foobar[one]two + 3} is "1002"
				 * Dont depend on this behavior.
				 */
				if (ptr && *ptr)
				{
					strcat(result2, ptr);
					result1 = next_unit(result2, args, arg_flag, stage);
				}
				else
				{
					if (!(result1 = find_inline(result2)))
						malloc_strcpy(&result1, empty_string);
				}
#ifndef __GNUC__
				new_free(&result2);
#endif
			}

			/*
			 * Notice of unexpected behavior:
			 *
			 * If $onetwo is "testing",
			 * /eval echo ${[one]two} returns "testing".
			 * Dont depend on this behavior.
			 */ 
			else if (ptr && *ptr)
			{
				malloc_strcat(&result1, ptr);
				result2 = next_unit(result1, args, arg_flag, stage);
				new_free(&result1);
				result1 = result2;
			}

			/*
			 * result1 shouldnt ever be pointing at an empty
			 * string here, but if it is, we just malloc_strcpy
			 * a new empty_string into it.  This fixes an icky
			 * memory hog bug my making sure that a (long) string
			 * with a leading null gets replaced by a (small) 
			 * string of size one.  Capish?
			 */
			if (!*result1)
				malloc_strcpy(&result1, empty_string);

			return result1;
		}

		/*
		 * The addition and subtraction operators have four contexts:
		 * 1) + is a binary additive operator if there is an rvalue
		 *	as the token to the left (ignored)
		 * 2) + is a unary magnitudinal operator if there is no 
		 *	rvalue to the left.
		 * 3) ++text or text++ is a unary pre/post in/decrement
		 *	operator.
		 * 4) += is the binary implied additive operator.
		 */
		case '-':
		case '+':
		{
			if (ptr[1] == ptr[0])
			{
				int prefix, display;
				long r;

				if (stage != NU_UNIT)
				{
					/* 
					 * only one 'ptr++' because a 2nd
					 * one is done at the top of the
					 * loop after the 'break'.
					 */
					ptr++;
					/*ptr = lastop(ptr); */
					break;
				}

				if (ptr == str)		/* prefix */
					prefix = 1, ptr2 = ptr + 2;
				else			/* postfix */
					prefix = 0, ptr2 = str, *ptr++ = 0;

				varname = expand_alias(NULL, ptr2, args, arg_flag, NULL);

				if (!(result1 = find_inline(varname)))
					malloc_strcpy(&result1,"0");

				r = atol(result1);
				if (*ptr == '+')
					r++;
				else    
					r--;

				display = window_display;
				window_display = 0;
				add_alias(VAR_ALIAS,varname,ltoa(r));
				window_display = display;

				if (!prefix)
					r--;

				new_free(&result1);
				new_free(&varname);
				return m_strdup(ltoa(r));
			}

			/* Unary op is ignored */
			else if (ptr == str)
				break;


#ifdef FLOATING_POINT_SUPPORT
			SETUP_FLOAT_OPERATION(NU_ADD)

			if (op == '-')
				dvalue3 = dvalue1 - dvalue2;
			else
				dvalue3 = dvalue1 + dvalue2;

			sprintf(tmp, "%f", dvalue3);
			canon_number(tmp);
#else
			SETUP_INTEGER_OPERATION(NU_ADD)

			if (op == '-')
				value3 = value1 - value2;
			else
				value3 = value1 + value2;

			strcpy(tmp, ltoa(value3));
#endif
			CLEANUP_IMPLIED()
			return m_strdup(tmp);
		}


		/*
		 * The Multiplication operators have two contexts:
		 * 1) * is a binary multiplicative op
		 * 2) *= is the implied binary multiplicative op
		 */
		case '/':
		case '*':
		case '%':
		{
			/* Unary op is ignored */
			if (ptr == str)
				break;

			/* default value on error */
			dvalue3 = 0.0;

			SETUP_FLOAT_OPERATION(NU_MULT)

			if (op == '*')
				dvalue3 = dvalue1 * dvalue2;
			else 
			{
				if (dvalue2 == 0.0)
					yell("Division by zero!");

				else if (op == '/')
					dvalue3 = dvalue1 / dvalue2;
				else
					dvalue3 = (int)dvalue1 % (int)dvalue2;
			}

			sprintf(tmp, "%f", dvalue3);
			canon_number(tmp);
			CLEANUP_IMPLIED()
			return m_strdup(tmp);
		}


		/*
		 * The # operator has three contexts:
		 * 1) ## is a binary string catenation operator
		 * 2) #= is an implied string catenation operator
		 */
		case '#':
		{
			if (ptr[1] == '#' && stage == NU_ADD)
			{
				*ptr++ = '\0';
				ptr++;
				result1 = next_unit(str, args, arg_flag, stage);
				result2 = next_unit(ptr, args, arg_flag, stage);
				malloc_strcat(&result1, result2);
				new_free(&result2);
				return result1;
			}

			else if (ptr[1] == '=' && stage == NU_ASSN)
			{
				char *sval1, *sval2;

				SETUP_IMPLIED(sval1, sval2, m_strdup)
				malloc_strcat(&sval1, sval2);
				new_free(&sval2);
				strmcpy(tmp, sval1, TMPSIZ);
				CLEANUP_IMPLIED()
				return sval1;
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}


	/* 
	 * Reworked - Jeremy Nelson, Feb 1994
	 * Reworked again, Feb 1996 (jfn)
	 * 
	 * X, XX, and X= are all supported, where X is one of "&" (and), 
	 * "|" (or) and "^" (xor).  The XX forms short-circuit, as they
	 * do in C and perl.  X and X= forms are bitwise, XX is logical.
	 */
		case '&':
		{
			/* && is binary short-circuit logical and */
			if (ptr[0] == ptr[1] && stage == NU_CONJ)
			{
				*ptr++ = '\0';
				ptr++;

				result1 = next_unit(str, args, arg_flag, stage);
				if (check_val(result1))
				{
					result2 = next_unit(ptr, args, arg_flag, stage);
					value3 = check_val(result2);
				}
				else
					value3 = 0;

				new_free(&result1);
				new_free(&result2);
				return m_strdup(value3 ? "1" : "0");
			}

			/* &= is implied binary bitwise and */
			else if (ptr[1] == '=' && stage == NU_ASSN)
			{
				SETUP_IMPLIED(value1, value2, atol)
				value1 &= value2;
				strmcpy(tmp, ltoa(value1), TMPSIZ);
				CLEANUP_IMPLIED();
				return m_strdup(tmp);
			}

			/* & is binary bitwise and */
			else if (ptr[1] != ptr[0] && ptr[1] != '=' && stage == NU_BITW)
			{
				SETUP_BINARY(value1, value2, atol)
				return m_strdup(ltoa(value1 & value2));
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}


		case '|':
		{
			/* || is binary short-circuiting logical or */
			if (ptr[0] == ptr[1] && stage == NU_CONJ)
			{
				*ptr++ = '\0';
				ptr++;

				result1 = next_unit(str, args, arg_flag, stage);
				if (!check_val(result1))
				{
					result2 = next_unit(ptr, args, arg_flag, stage);
					value3 = check_val(result2);
				}
				else
					value3 = 1;

				new_free(&result1);
				new_free(&result2);
				return m_strdup(value3 ? "1" : "0");
			}

			/* |= is implied binary bitwise or */
			else if (ptr[1] == '=' && stage == NU_ASSN)
			{
				SETUP_IMPLIED(value1, value2, atol)
				value1 |= value2;
				strmcpy(tmp, ltoa(value1), TMPSIZ);
				CLEANUP_IMPLIED();
				return m_strdup(tmp);
			}

			/* | is binary bitwise or */
			else if (ptr[1] != ptr[0] && ptr[1] != '=' && stage != NU_BITW)
			{
				SETUP_BINARY(value1, value2, atol)
				return m_strdup(ltoa(value1 | value2));
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		case '^':
		{
			/* ^^ is binary logical xor */
			if (ptr[0] == ptr[1] && stage == NU_CONJ)
			{
				*ptr++ = '\0';
				ptr++;

				value1 = check_val((result1 = next_unit(str, args, arg_flag, stage)));
				value2 = check_val((result2 = next_unit(ptr, args, arg_flag, stage)));
				new_free(&result1);
				new_free(&result2);

				return m_strdup(value1 ^ value2 ? "1" : "0");
			}

			/* ^= is implied binary bitwise xor */
			else if (ptr[1] == '=' && stage == NU_ASSN)  /* ^= op */
			{

				SETUP_IMPLIED(value1, value2, atol)
				value1 ^= value2;
				strmcpy(tmp, ltoa(value1), TMPSIZ);
				CLEANUP_IMPLIED();
				return m_strdup(tmp);
			}

			/* ^ is binary bitwise xor */
			else if (ptr[1] != ptr[0] && ptr[1] != '=' && stage == NU_BITW)
			{
				SETUP_BINARY(value1, value2, atol)
				return m_strdup(ltoa(value1 ^ value2));
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		/*
		 * ?: is the tertiary operator.  Confusing.
		 */
		case '?':
		{
			if (stage == NU_TERT)
			{
				*ptr++ = '\0';
				result1 = next_unit(str, args, arg_flag, stage);
				ptr2 = MatchingBracket(ptr, '?', ':');

				/* Unbalanced :, or possibly missing */
				if (!ptr2)  /* ? but no :, ignore */
				{
					ptr = lastop(ptr);
					break;
				}
				*ptr2++ = '\0';
				if ( check_val(result1) )
					result2 = next_unit(ptr, args, arg_flag, stage);
				else
					result2 = next_unit(ptr2, args, arg_flag, stage);

				/* XXXX - needed? */
				*(ptr2-1) = ':';
				new_free(&result1);
				return result2;
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		/*
		 * = is the binary assignment operator
		 * == is the binary equality operator
		 */
		case '=':
		{
			if (ptr[1] != '=' && stage == NU_ASSN)
			{
				*ptr++ = '\0';
				result1 = expand_alias(NULL, str, args, arg_flag, NULL);
				result2 = next_unit(ptr, args, arg_flag, stage);

				lastc = result1 + strlen(result1) - 1;
				while (lastc > result1 && *lastc == ' ')
					*lastc-- = '\0';
				for (varname = result1; my_isspace(*varname);)
					varname++;

				display = window_display;
				window_display = 0;

				if (*varname)
					add_alias(VAR_ALIAS, varname, result2);
				else
					yell("Invalid assignment: no lvalue");

				window_display = display;
				new_free(&result1);
				return result2;
			}

			else if (ptr[1] == '=' && stage == NU_COMP)
			{
				*ptr++ = '\0';
				ptr++;
				result1 = next_unit(str, args, arg_flag, stage);
				result2 = next_unit(ptr, args, arg_flag, stage);
				if (!my_stricmp(result1, result2))
					malloc_strcpy(&result1, "1");
				else
					malloc_strcpy(&result1, "0");
				new_free(&result2);
				return result1;
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		/*
		 * < is the binary relative operator
		 * << is the binary bitwise shift operator (not supported)
		 */
		case '>':
		case '<':
		{
			if (ptr[1] == ptr[0] && stage == NU_BITW)  /* << or >> op */
			{
				/* Not supported yet */
				yell("<< or >> not supported.");
				break;
			}

			else if (ptr[1] != ptr[0] && stage == NU_COMP)
			{
				op = *ptr;
				if (ptr[1] == '=')
					value3 = 1, *ptr++ = '\0';
				else
					value3 = 0;

				*ptr++ = '\0';
				result1 = next_unit(str, args, arg_flag, stage);
				result2 = next_unit(ptr, args, arg_flag, stage);

/* erf erf erf. */
#define my_isdigit(x) \
(*x >= '0' && *x <= '9') || \
((*x == '-'  || *x == '+') && (x[1] >= '0' && x[1] <= '9'))

				if ((my_isdigit(result1)) && (my_isdigit(result2)))
				{
					dvalue1 = atof(result1);
					dvalue2 = atof(result2);
					value1 = (dvalue1 == dvalue2) ? 0 : ((dvalue1 < dvalue2) ? -1 : 1);
				}
				else
					value1 = my_stricmp(result1, result2);

				if (value1)
				{
					value2 = (value1 > 0) ? 1 : 0;
					if (op == '<')
						value2 = 1 - value2;
				}
				else
					value2 = value3;

				new_free(&result1);
				new_free(&result2);
				return m_strdup(ltoa(value2));
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		/*
		 * ~ is the 1s complement (bitwise negation) operator
		 */
		case '~':
		{
			if (ptr == str && stage == NU_UNIT)
			{
				result1 = next_unit(str+1, args, arg_flag, stage);
				if (isdigit(*result1))
					value1 = ~atol(result1);
				else
					value1 = 0;

				return m_strdup(ltoa(value1));
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		/*
		 * ! is the 2s complement (logical negation) operator
		 * != is the inequality operator
		 */
		case '!':
		{
			if (ptr == str && stage == NU_UNIT)
			{
				result1 = next_unit(str+1, args, arg_flag, stage);

				if (my_isdigit(result1))
				{
					value1 = atol(result1);
					value2 = value1 ? 0 : 1;
				}
				else
					value2 = ((*result1)?0:1);

				new_free(&result1);
				return m_strdup(ltoa(value2));
			}

			else if (ptr != str && ptr[1] == '=' && stage == NU_COMP)
			{
				*ptr++ = '\0';
				ptr++;

				result1 = next_unit(str, args, arg_flag, stage);
				result2 = next_unit(ptr, args, arg_flag, stage);

				if (!my_stricmp(result1, result2))
					malloc_strcpy(&result1, "0");
				else
					malloc_strcpy(&result1, "1");

				new_free(&result2);
				return result1;
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}


		/*
		 * , is the binary right-hand operator
		 */
		case ',': 
		{
			if (stage == NU_EXPR)
			{
				*ptr++ = '\0';
				result1 = next_unit(str, args, arg_flag, stage);
				result2 = next_unit(ptr, args, arg_flag, stage);
				new_free(&result1);
				return result2;
			}

			else
			{
				ptr = lastop(ptr);
				break;
			}
		}

		} /* end of switch */
	}

	/*
	 * If were not done parsing, parse it again.
	 */
	if (stage != NU_UNIT)
		return next_unit(str, args, arg_flag, stage + 1);

	/*
	 * If the result is a number, return it.
	 */
	if (my_isdigit(str))
		return m_strdup(str);


	/*
	 * If the result starts with a #, or a @, its a special op
	 */
	if (*str == '#' || *str == '@')
		op = *str++;
	else
		op = '\0';

	/*
	 * Its not a number, so its a variable, look it up.
	 */
	if (!(result1 = find_inline_with_args(str, args, arg_flag)))
		return m_strdup(empty_string);

	/*
	 * See if we have to take strlen or word_count on the variable.
	 */
	if (op)
	{
		if (op == '#')
			value1 = word_count(result1);
		else if (op == '@')
			value1 = strlen(result1);
		new_free(&result1);
		return m_strdup(ltoa(value1));
	}

	/*
	 * Nope.  Just return the variable.
	 */
	return result1;
}

/*
 * parse_inline:  This evaluates user-variable expression.  I'll talk more
 * about this at some future date. The ^ function and some fixes by
 * troy@cbme.unsw.EDU.AU (Troy Rollo) 
 */
extern
#ifdef __STDC__
char	*parse_inline(char *str, char *args, int *args_flag)
#else
char	*parse_inline(str, args, args_flag)
	char	*str;
	char	*args;
	int	*args_flag;
#endif
{
	return next_unit(str, args, args_flag, NU_EXPR);
}


#ifdef __STDC__
static void	do_alias_string (char unused, char *not_used)
#else
static void do_alias_string (unused, not_used)
char unused, *not_used;
#endif
{
	malloc_strcpy(&alias_string, get_input());
}

/*
 * TruncateAndQuote: This handles string width formatting and quoting for irc 
 * variables when [] or ^x is specified.  
 */
#ifdef __STDC__
static	void	TruncateAndQuote(char **buff, char *add, int length, char *quote_em)
#else
static	void	TruncateAndQuote(buff, add, length, quote_em)
	char	**buff,
		*add;
	int	length;
	char	*quote_em;
#endif
{
	/* Semantics:
	 *	If length is nonzero, then "add" will be truncated
		to "length" characters
	 * 	If length is zero, nothing is done to "add"

	 *	If quote_em is not NULL, then the resulting string
		will be quoted and appended to "buff"
	 *	If quote_em is NULL, then the value of "add" is
		appended to "buff"
	 */
	char	*ptr;
	char	*buffer = NULL; /* oh, what the heck */

	if (length)
	{
		/* ugh. */
		buffer = (char *)new_malloc((length > 0?length:-length)+1);
		strformat(buffer, add, length);
		add = buffer;
	}
	if (quote_em)
	{
		ptr = double_quote(add, quote_em);
		malloc_strcat(buff, ptr);
		new_free(&ptr);
	}
	else if (buff)
		malloc_strcat(buff, add);

	if (buffer)
		new_free(&buffer);

	return;
}

/*
 * alias_special_char: Here we determine what to do with the character after
 * the $ in a line of text. The special characters are described more fully
 * in the help/ALIAS file.  But they are all handled here. Parameters are the
 * return char ** pointer to which things are placed,
 * a ptr to the string (the first character of which is the special
 * character), the args to the alias, and a character indication what
 * characters in the string should be quoted with a backslash.  It returns a
 * pointer to the character right after the converted alias.
 * 
 * The args_flag is set to 1 if any of the $n, $n-, $n-m, $-m, $*, or $() 
 * is used in the alias.  Otherwise it is left unchanged.
 */
#ifdef __STDC__
static	char	*alias_special_char(char **buffer, char *ptr, char *args, char *quote_em, int *args_flag)
#else
static	char	*alias_special_char(buffer, ptr, args, quote_em,args_flag)
	char	**buffer;
	char	*ptr;
	char	*args;
	char	*quote_em;
	int	*args_flag;
#endif
{
	char	*tmp,
		*tmp2,
		c;
	int	upper,
		lower,
		length;

	length = 0;
	if ((c = *ptr) == LEFT_BRACKET)
	{
		ptr++;
		if ((tmp = (char *) index(ptr, RIGHT_BRACKET)) != NULL)
		{
			*(tmp++) = (char) 0;
			length = atoi(ptr);
			ptr = tmp;
			c = *ptr;
		}
		else
		{
			say("Missing %c", RIGHT_BRACKET);
			return (ptr);
		}
	}
	tmp = ptr+1;
	switch (c)
	{
		case LEFT_PAREN:
		{
			char *sub_buffer = NULL;

			if ((ptr = MatchingBracket(tmp, LEFT_PAREN, RIGHT_PAREN)) || 
			    (ptr = (char *) index(tmp, RIGHT_PAREN)))
				*(ptr++) = (char) 0;
			tmp = expand_alias((char *) 0, tmp, args, args_flag, NULL);
			alias_special_char(&sub_buffer, tmp, args, quote_em, args_flag);
			TruncateAndQuote(buffer, sub_buffer, length, quote_em);
			new_free(&sub_buffer);
			new_free(&tmp);
			*args_flag = 1;
			return (ptr);
		}
		case '!':
		{
			if ((ptr = (char *) index(tmp, '!')) != NULL)
				*(ptr++) = (char) 0;
			if ((tmp = do_history(tmp, empty_string)) != NULL)
			{
				TruncateAndQuote(buffer, tmp, length, quote_em);
				new_free(&tmp);
			}
			return (ptr);
		}
		case LEFT_BRACE:
		{
			/* BLAH. This didnt allow for nesting before.  How lame. */
			ptr = MatchingBracket(tmp, LEFT_BRACE, RIGHT_BRACE);
			if (!ptr)
			{
				yell("Unmatched %c", LEFT_BRACE);
				break;
			}
			*(ptr++) = (char) 0;
			if ((tmp = parse_inline(tmp, args, args_flag)) != NULL)
			{
				TruncateAndQuote(buffer, tmp, length, quote_em);
				new_free(&tmp);
			}
			return (ptr);
		}
		case DOUBLE_QUOTE:
		{
			if ((ptr = (char *) index(tmp, DOUBLE_QUOTE)) != NULL)
				*(ptr++) = (char) 0;
			alias_string = (char *) 0;
			get_line(tmp, 0, do_alias_string);
			TruncateAndQuote(buffer, alias_string, length, quote_em);
			new_free(&alias_string);
			return (ptr);
		}
		case '*':
		{
			TruncateAndQuote(buffer, args, length, quote_em);
			*args_flag = 1;
			return (ptr + 1);
		}

		/* ok, ok. so i did forget something. */
		case '#':
		case '@':
		{
			char c2 = 0;
			char *sub_buffer = NULL;
			char *rest, *val;

			if ((rest = sindex(ptr+1, alias_illegals)))
			{
				c2 = *rest;
				*rest = 0;
			}

			alias_special_char(&sub_buffer, ptr+1, args, quote_em, args_flag);
			if (c == '#')
				val = m_strdup(ltoa(word_count(sub_buffer)));
			else
				val = m_strdup(ltoa(strlen(sub_buffer)));

			TruncateAndQuote(buffer, val, length, quote_em);
			new_free(&val);
			new_free(&sub_buffer);

			if (rest)
				*rest = c2;

			return rest;
		}

		default:
		{
			if (isdigit(c) || (c == '-') || c == '~')
			{
				*args_flag = 1;
				if (c == '~')
				{
					/* double check to make sure $~ still works */
					lower = upper = EOS;
					ptr++;
				}
				else if (c == '-')
				{
					/* special case.. leading spaces are
					 * always retained when you do $-n,
					 * even if 'n' is 0.  The stock client
					 * stripped spaces on $-0, which is
					 * not correct.
					 */
					lower = SOS;
					ptr++;
					upper = parse_number(&ptr);
					if (upper == -1)
						return empty_string; /* error */
				}
				else
				{
					lower = parse_number(&ptr);
					if (*ptr == '-')
					{
						ptr++;
						upper = parse_number(&ptr);
						if (upper == -1)
							upper = EOS;
					}
					else
						upper = lower;
				}

				/*
				 * Protect against a crash.  There
				 * are some gross syntactic errors
				 * that can be made that will result
				 * in ''args'' being NULL here.  That
				 * will crash the client, so we have
				 * to protect against that by simply
				 * chewing the expando.
				 */
				if (!args)
					tmp2 = m_strdup(empty_string);
				else
					tmp2 = extract2(args, lower, upper);

				TruncateAndQuote(buffer, tmp2, length, quote_em);
				new_free(&tmp2);
				return (ptr ? ptr : empty_string);
			}
			else
			{
				char	*rest,
					c = (char) 0;
				int	function_call = 0;

			/*
			 * Why use ptr+1?  Cause try to maintain backward compatability
			 * can be a pain in the butt.  Basically, we don't want any of
			 * the illegal characters in the alias, except that things like
			 * $* and $, were around first, so they must remain legal.  So
			 * we skip the first char after the $.  Does this make sense?
			 *
			 * Ok.  All together now -- "NO, IT DOESNT."
			 */
				/* special case for $ */
				if (*ptr == '$')
				{
					rest = ptr+1;
					c = *rest;
					*rest = (char) 0;
				}

				else if ((rest = sindex(ptr+1, alias_illegals)) != NULL)
				{
					if (isalpha(*ptr) || *ptr == '_')
					{
						/* its an array reference. */
						if (*rest == LEFT_BRACKET)
						{
							while (*rest == LEFT_BRACKET)
							{
								if ((tmp = MatchingBracket(rest + 1, LEFT_BRACKET, RIGHT_BRACKET)))
									rest = tmp+1;
								else
									/* Cross your fingers and hope for the best */
									break;
							}
						}

						/* Its a function call */
						else if (*rest == LEFT_PAREN)
						{
							char *saver;
							function_call = 1;
							*rest++ = 0;
							saver = rest;
							if (!(tmp = MatchingBracket(rest, LEFT_PAREN, RIGHT_PAREN)))
								function_call = 0;
							else 
							{
								*tmp++ = 0;
								rest = tmp;
							}
							tmp = saver;
						}
					}
					c = *rest;
					*rest = (char) 0;
				}


				if (function_call)
					tmp = call_function(ptr, tmp, args, args_flag);
				else
					tmp = find_inline_with_args(ptr, args, args_flag);

				if (tmp)
				{
					TruncateAndQuote(buffer, tmp, length, quote_em);
					new_free(&tmp);
				}

				if (rest)
					*rest = c;
				return(rest);
			}
		}
	}
	return NULL;
}


/*
 * expand_alias: Expands inline variables in the given string and returns the
 * expanded string in a new string which is malloced by expand_alias(). 
 *
 * Also unescapes anything that was quoted with a backslash
 *
 * Behaviour is modified by the following:
 *	Anything between brackets (...) {...} is left unmodified.
 *	If more_text is supplied, the text is broken up at
 *		semi-colons and returned one at a time. The unprocessed
 *		portion is written back into more_text.
 *	Backslash escapes are unescaped.
 */
extern
#ifdef __STDC__
char	*expand_alias(char *name, char *string, char *args, int *args_flag, char **more_text)
#else
char	*expand_alias(name, string, args,args_flag, more_text)
	char	*name,
		*string,
		*args;
	int	*args_flag;
	char	**more_text;
#endif
{
	char	*buffer = NULL,
		*ptr,
		*stuff = (char *) 0,
		*free_stuff,
		*quote_str = (char *) 0;
	char	quote_temp[2];
	char	ch;
	int	is_quote = 0;
	int	unescape = 1;

	if (!string || !*string)
		return m_strdup(empty_string);

	if (*string == '@' && more_text)
	{
		unescape = 0;
		*args_flag = 1; /* Stop the @ command from auto appending */
	}
	quote_temp[1] = 0;

	malloc_strcpy(&stuff, string);
	free_stuff = stuff;
	ptr = stuff;
	if (more_text)
		*more_text = NULL;

	while (ptr && *ptr)
	{
		if (is_quote)
		{
			is_quote = 0;
			++ptr;
			continue;
		}
		switch(*ptr)
		{
		case '$':
		{
	/*
	 * The test here ensures that if we are in the expression
	 * evaluation command, we don't expand $. In this case we
	 * are only coming here to do command separation at ';'s.
	 * If more_text is not defined, and the first character is
	 * '@', we have come here from [] in an expression.
	 */
			if (more_text && *string == '@')
			{
				ptr++;
				break;
			}
			*(ptr++) = 0;
			m_strcat_ues(&buffer, stuff, unescape);

			while (*ptr == '^')
			{
				quote_temp[0] = *++ptr;
				malloc_strcat(&quote_str, quote_temp);
				ptr++;
			}
			stuff = alias_special_char(&buffer, ptr, args, quote_str, args_flag);
			if (stuff)
				new_free(&quote_str);
			ptr = stuff;
			break;
		}
		case ';':
		{
			if (!more_text)
			{
				ptr++;
				break;
			}
			*more_text = string + (ptr - free_stuff) +1;
			*ptr = '\0'; /* To terminate the loop */
			break;
		}
		case LEFT_PAREN:
		case LEFT_BRACE:
		{
			ch = *ptr;
			*ptr = '\0';
			m_strcat_ues(&buffer, stuff, unescape);
			stuff = ptr;
			*args_flag = 1;
			if (!(ptr = MatchingBracket(stuff + 1, ch,
					(ch == LEFT_PAREN) ?
					RIGHT_PAREN : RIGHT_BRACE)))
			{
				yell("Unmatched %c", ch);
				ptr = stuff + strlen(stuff+1)+1;
			}
			else
				ptr++;

			*stuff = ch;
			ch = *ptr;
			*ptr = '\0';
			malloc_strcat(&buffer, stuff);
			stuff = ptr;
			*ptr = ch;
			break;
		}
		case '\\':
		{
			is_quote = 1;
			ptr++;
			break;
		}
		default:
			ptr++;
			break;
		}
	}
	if (stuff)
		m_strcat_ues(&buffer, stuff, unescape);

	new_free(&free_stuff);
	if (get_int_var(DEBUG_VAR) & DEBUG_EXPANSIONS)
		yell("Expanded [%s] to [%s]", string, buffer);

	return buffer;
}

/*
 * get_alias: returns the alias matching 'name' as the function value. 'args'
 * are expanded as needed, etc.  If no matching alias is found, null is
 * returned, cnt is 0, and full_name is null.  If one matching alias is
 * found, it is retuned, with cnt set to 1 and full_name set to the full name
 * of the alias.  If more than 1 match are found, null is returned, cnt is
 * set to the number of matches, and fullname is null. NOTE: get_alias()
 * mallocs the space for the full_name, but returns the actual value of the
 * alias if found! 
 */
extern
#ifdef __STDC__
char	*get_alias(int type, char *name, int *cnt, char **full_name)
#else
char	*get_alias(type, name, cnt, full_name)
	int	type;
	char	*name,
		**full_name;
	int	*cnt;
#endif
{
	Alias	*tmp;

	*full_name = NULL;
	if (!name || !*name)
	{
		*cnt = 0;
		return NULL;
	}

	if ((tmp = find_alias(&(alias_list[type]), name, 0, cnt, 1)) != NULL)
	{
		if (*cnt < 2)
		{
			malloc_strcpy(full_name, tmp->name);
			return (tmp->stuff);
		}
	}
	return NULL;
}

/*
 * match_alias: this returns a list of alias names that match the given name.
 * This is used for command completion etc.  Note that the returned array is
 * malloced in this routine.  Returns null if no matches are found 
 */
extern
#ifdef __STDC__
char	**match_alias(char *name, int *cnt, int type)
#else
char	**match_alias(name, cnt, type)
	char	*name;
	int	*cnt;
	int	type;
#endif
{
	Alias	*tmp;
	char	**matches = (char **) 0;
	int	matches_size = 5;
	int	len;
	char	*last_match = (char *) 0;
	char	*dot;

	len = strlen(name);
	*cnt = 0;
	matches = (char	**) new_malloc(sizeof(char *) * matches_size);
	for (tmp = alias_list[type]; tmp; tmp = tmp->next)
	{
		if (strncmp(name, tmp->name, len) == 0)
		{
			if ((dot = (char *) index(tmp->name+len, '.')) != NULL)
			{
				if (type == COMMAND_ALIAS)
					continue;
				else
				{
					*dot = '\0';
					if (last_match && !strcmp(last_match, tmp->name))
					{
						*dot = '.';
						continue;
					}
				}
			}
			matches[*cnt] = (char *) 0;
			malloc_strcpy(&(matches[*cnt]), tmp->name);
			last_match = matches[*cnt];
			if (dot)
				*dot = '.';
			if (++(*cnt) == matches_size)
			{
				matches_size += 5;
				matches = (char	**) new_realloc((char *)matches,
					sizeof(char *) * matches_size);
			}
		}
		else if (*cnt)
			break;
	}
	if (*cnt)
	{
		matches = (char	**) new_realloc((char *)matches, sizeof(char *) * (*cnt + 1));
		matches[*cnt] = (char *) 0;
	}
	else
		new_free((char **)&matches);
	return (matches);
}

/* delete_alias: The alias name is removed from the alias list. */
extern
#ifdef __STDC__
void delete_alias(int type, char *name)
#else
void delete_alias(type, name)
	int	type;
	char	*name;
#endif
{
	Alias	*tmp;

	upper(name);

	/* we dont need to unstub it if we're just going to delete it */
	if ((tmp = find_alias(&(alias_list[type]), name, 1, (int *) NULL, 0))
			!= NULL)
	{
		new_free(&(tmp->name));
		new_free(&(tmp->stuff));
		new_free((char **)&tmp);
		if (type == COMMAND_ALIAS)
			say("Alias	%s removed", name);
		else
			say("Assign %s removed", name);
	}
	else
		say("No such alias: %s", name);
}

/*
 * list_aliases: Lists all aliases matching 'name'.  If name is null, all
 * aliases are listed 
 */
#ifdef __STDC__
static void list_aliases(int type, char *name)
#else
static void list_aliases(type, name)
	int	type;
	char	*name;
#endif
{
	Alias	*tmp;
	int	len;
	int	DotLoc,
		LastDotLoc = 0;
	char	*LastStructName = NULL;
	char	*s;

	if (type == COMMAND_ALIAS)
		say("Aliases:");
	else
		say("Assigns:");

	if (name)
	{
		upper(name);
		len = strlen(name);
	}
	else
		len = 0;

	for (tmp = alias_list[type]; tmp; tmp = tmp->next)
	{
		if (!name || !strncmp(tmp->name, name, len))
		{
			s = index(tmp->name + len, '.');
			if (!s)
			{
				if (tmp->stub)
					say("\t%s STUBBED TO %s", tmp->name, tmp->stub);
				else
					say("\t%s\t%s", tmp->name, tmp->stuff);
			}
			else
			{
				DotLoc = s - tmp->name;
				if (!LastStructName || (DotLoc != LastDotLoc) || strncmp(tmp->name, LastStructName, DotLoc))
				{
					say("\t%*.*s\t<Structure>", DotLoc, DotLoc, tmp->name);
					LastStructName = tmp->name;
					LastDotLoc = DotLoc;
				}
			}
		}
	}
}

/*
 * mark_alias: sets the mark field of the given alias to 'flag', and returns
 * the previous value of the mark.  If the name is not found, -1 is returned.
 * This is used to prevent recursive aliases by marking and unmarking
 * aliases, and not reusing an alias that has previously been marked.  I'll
 * explain later 
 */
static
#ifdef __STDC__
int mark_alias(char *name, int flag)
#else
int mark_alias(name, flag)
	char	*name;
	int	flag;
#endif
{
	int	old_mark;
	Alias	*tmp;
	int	match;

	if ((tmp = find_alias(&(alias_list[COMMAND_ALIAS]), name, 0, &match, 1)) != NULL)
	{
		if (match < 2)
		{
			old_mark = tmp->mark;
		/* New handling of recursion */
			if (flag)
			{
				int	i;
				/* Count recursion */

				tmp->mark = tmp->mark + flag;
				if ((i = get_int_var(MAX_RECURSIONS_VAR)) > 1)
				{
					if (tmp->mark > i)
					{
						tmp->mark = 0;
						return(1); /* MAX exceeded. */
					}
					else return(0);
				/* In recursion but it's ok */
				}
				else
				{
					if (tmp->mark > 1)
					{
						tmp->mark = 0;
						return(1);
				/* max of 1 here.. exceeded */
					}
					else return(0);
				/* In recursion but it's ok */
				}
			}
			else
		/* Not in recursion at all */
			{
				tmp->mark = 0;
				return(old_mark);
			/* This one gets ignored anyway */
			}
		}
	}
	return (-1);
}

/*
 * execute_alias: After an alias has been identified and expanded, it is sent
 * here for proper execution.  This routine mainly prevents recursive
 * aliasing.  The name is the full name of the alias, and the alias is
 * already expanded alias (both of these parameters are returned by
 * get_alias()) 
 */
#ifdef __STDC__
extern void execute_alias(char *alias_name, char *alias, char *args)
#else
extern void execute_alias(alias_name, alias, args)
	char	*alias_name,
		*alias,
		*args;
#endif
{
	if (mark_alias(alias_name, 1))
		say("Maximum recursion count exceeded in: %s", alias_name);
	else
	{
		parse_line(alias_name, alias, args, 0,1);
		mark_alias(alias_name, 0);
	}
}

/*
 * save_aliases: This will write all of the aliases to the FILE pointer fp in
 * such a way that they can be read back in using LOAD or the -l switch 
 */
extern
#ifdef __STDC__
void save_aliases(FILE *fp, int do_all)
#else
void save_aliases(fp, do_all)
	FILE	*fp;
	int	do_all;
#endif
{
	Alias	*tmp;

	for (tmp = alias_list[VAR_ALIAS]; tmp; tmp = tmp->next)
	{
		if (!tmp->global || do_all)
		{
			if (tmp->stub)
				fprintf(fp, "STUB ASSIGN %s %s\n", tmp->name, tmp->stub);
			else
				fprintf(fp, "ASSIGN %s %s\n", tmp->name, tmp->stuff);
		}
	}
	for (tmp = alias_list[COMMAND_ALIAS]; tmp; tmp = tmp->next)
	{
		if (!tmp->global || do_all)
		{
			if (tmp->stub)
				fprintf(fp, "STUB ALIAS %s %s\n", tmp->name, tmp->stub);
			else
				fprintf(fp, "ALIAS %s %s\n", tmp->name, tmp->stuff);
		}
	}	
}


#ifdef __STDC__
extern  void	flush_aliases(char *command,char *args, char *subargs)
#else
extern  void	flush_aliases(command,args,subargs)
	char 	*command,
		*args;
	char	*subargs;
#endif
{
	char 	*blah;

	say("*** I hope you didnt have anything valuable in there...");
	if (!args || !*args)
	{
		flush_aliases2();
		flush_variables();
		flush_on_hooks();
		return;
	}
	while ((blah = next_arg(args, &args)))
	{
		if (!my_strnicmp(blah,"VAR",1))
			flush_variables();
		else if (!my_strnicmp(blah,"ALIAS",3))
			flush_aliases2();
		else if (!my_strnicmp(blah,"ON",1))
			flush_on_hooks();
		else if (!my_strnicmp(blah,"ALL",3))
		{
			flush_aliases2();
			flush_variables();
			flush_on_hooks();
		}
	}
}

static void flush_aliases2 _((void))
{
	Alias	*tmp;
	Alias   *ntmp;

	for (tmp = alias_list[COMMAND_ALIAS]; tmp;)
	{
		/* we dont need to unstub if we're just going to flush it. */
		tmp = find_alias(&alias_list[COMMAND_ALIAS],tmp->name,1,NULL, 0);
		new_free(&tmp->stuff);
		new_free(&tmp->name);
		ntmp = tmp->next;
		new_free((char **)&tmp);
		tmp = ntmp;
	}
	alias_list[COMMAND_ALIAS] = (Alias *) 0;
}

static void flush_variables _((void))
{
	Alias	*tmp;
	Alias   *ntmp;

	for (tmp = alias_list[VAR_ALIAS]; tmp;)
	{
		/* we dont need to unstub if we're just going to flush it. */
		tmp = find_alias(&alias_list[VAR_ALIAS],tmp->name,1,NULL, 0);
		new_free(&tmp->stuff);
		new_free(&tmp->name);
		ntmp = tmp->next;
		new_free((char **)&tmp);
		tmp = ntmp;
	}
	alias_list[VAR_ALIAS] = (Alias *) 0;
}



/*
 * alias: the /ALIAS command.  Calls the correct alias function depending on
 * the args 
 */
extern
#ifdef __STDC__
void	alias(char *command, char *args, char *subargs)
#else
void	alias(command, args, subargs)
	char	*command,
		*args;
	char	*subargs;
#endif
{
	char	*name,
		*rest;
	int	type;
	char	*ArrayIndex;
	char	*EndIndex;

	type = *command - 48;	/*
				 * A trick!  Yes, well, what the hell.  Note
				 * the the command part of ALIAS is "0" and
				 * the command part of ASSIGN is "1" in the
				 * command array list
				 */

	if ((name = next_arg(args, &rest)) != NULL)
	{
		/* XXXX - How about calling remove_brackets() here? */

		while ((ArrayIndex = (char *) index(name, '[')) != NULL)
		{
			*ArrayIndex++ = '.';
			if ((EndIndex = MatchingBracket(ArrayIndex, LEFT_BRACKET, RIGHT_BRACKET)) != NULL)
			{
				*EndIndex++ = '\0';
				strcat(name, EndIndex);
			}
			else
				break;
		}
/* 
 * Crowman reported this bug -- why the heck dont we strip off spaces
 * between the alias name and its first argument? (???) (E3P3.3, fall 1995)
 */
		while (my_isspace(*rest))
			rest++;

		if (*rest)
		{
			if (*rest == LEFT_BRACE)
			{
				char	*ptr = MatchingBracket(++rest, LEFT_BRACE, RIGHT_BRACE);
				if (!ptr)
					say("Unmatched brace in ALIAS or ASSIGN");
				else 
				{
					*ptr++ = 0;
					while (*ptr && my_isspace(*ptr))
						ptr++;

					if (*ptr)
						say("Junk after closing brace in ALIAS or ASSIGN");

					/*
					 * This fixes a rather obscure bug
					 * with /alias foobar { @ bar = 1 }
					 * being mis-interpreted.
					 */
					while (*rest && my_isspace(*rest))
						rest++;

					add_alias(type, name, rest);
				}
			}
			else
				add_alias(type, name, rest);
		}
		else
		{
			if (*name == '-')
			{
				if (*(name + 1))
					delete_alias(type, name + 1);
				else
					say("You must specify an alias to be removed");
			}
			else
				list_aliases(type, name);
		}
	}
	else
		list_aliases(type, (char *) 0);
}

/* stub command -- makes a stub for an alias/assign */
extern
#ifdef __STDC__
void stubcmd (char *command, char *args, char *subargs)
#else
void stubcmd (command, args, subargs)
char *command;
char *args;
char *subargs;
#endif
{
	char	*name,
		*rest;
	char	*type_c;
	int	type;
	char	*ArrayIndex;
	char	*EndIndex;

	type_c = next_arg(args, &rest);
	if (!type_c || !*type_c)
	{
		yell("Usage: /STUB (ALIAS|ASSIGN) alias-name filename");
		return;
	}
	else
	{
		if (!my_strnicmp(type_c, "ASSIGN", 6))
			type = 1;
		else if (!my_strnicmp(type_c, "ALIAS", 5))
			type = 0;
		else
		{
			yell("Usage: /STUB (ALIAS|ASSIGN) alias-name filename");
			return;
		}
	}

	name = next_arg(rest, &rest);
	if (!name || !*name)
	{
		yell("Usage: /STUB (ALIAS|ASSIGN) alias-name filename");
		return;
	}
	else
	{
		while ((ArrayIndex = (char *) index(name, '[')) != NULL)
		{
			*ArrayIndex++ = '.';
			if ((EndIndex = MatchingBracket(ArrayIndex, LEFT_BRACKET, RIGHT_BRACKET)) != NULL)
			{
				*EndIndex++ = '\0';
				strcat(name, EndIndex);
			}
			else
				break;
		}
	}
	if (!rest || !*rest)
	{
		yell("Usage: /STUB (ALIAS|ASSIGN) alias-name filename");
		return;
	}

	add_stub(type, name, rest);
}

/* built in expando functions */
static	char	*alias_line 		_((void)) { return m_strdup(get_input()); }
static	char	*alias_buffer 		_((void)) { return m_strdup(cut_buffer); }
static	char	*alias_time 		_((void)) { return m_strdup(update_clock(GET_TIME)); }
static	char	*alias_dollar 		_((void)) { return m_strdup("$"); }
static	char	*alias_detected 	_((void)) { return m_strdup(last_notify_nick); }
static	char	*alias_nick 		_((void)) { return m_strdup((curr_scr_win->server != -1) ? get_server_nickname(curr_scr_win->server) : empty_string); }
static	char	*alias_away 		_((void)) { return m_strdup((curr_scr_win->server != -1) ? server_list[curr_scr_win->server].away ? server_list[curr_scr_win->server].away : empty_string : empty_string); }
static	char	*alias_sent_nick 	_((void)) { return m_strdup((sent_nick) ? sent_nick : empty_string); }
static	char	*alias_recv_nick 	_((void)) { return m_strdup((recv_nick) ? recv_nick : empty_string); }
static	char	*alias_msg_body 	_((void)) { return m_strdup((sent_body) ? sent_body : empty_string); }
static	char	*alias_joined_nick 	_((void)) { return m_strdup((joined_nick) ? joined_nick : empty_string); }
static	char	*alias_public_nick 	_((void)) { return m_strdup((public_nick) ? public_nick : empty_string); }
static  char    *alias_show_realname 	_((void)) { return m_strdup(realname); }
static	char	*alias_version_str 	_((void)) { return m_strdup(irc_version); }
static	char	*alias_invite 		_((void)) { return m_strdup((invite_channel) ? invite_channel : empty_string); }
static	char	*alias_oper 		_((void)) { return m_strdup((from_server != -1) ? get_server_operator(from_server) ?  get_string_var(STATUS_OPER_VAR) : empty_string : empty_string); }
static	char	*alias_version 		_((void)) { return m_strdup(internal_version); }
static  char    *alias_online 		_((void)) { return m_sprintf("%ld",(long)start_time); }
static  char    *alias_idle 		_((void)) { return m_sprintf("%ld",time(NULL)-idle_time); }
static  char    *alias_show_userhost 	_((void)) { return m_sprintf("%s@%s",username, hostname); }
static	char	*alias_current_numeric	_((void)) { return m_sprintf("%03d", -current_numeric); }

static	char	*alias_currdir  	_((void))
{
	char 	*tmp = (char *)new_malloc(MAXPATHLEN+1);
	return getcwd(tmp, MAXPATHLEN);
}

static	char	*alias_channel 		_((void)) 
{ 
	char	*tmp; 
	return m_strdup((tmp = get_channel_by_refnum(0)) ? tmp : "0");
}

static	char	*alias_server 		_((void)) 
{
	return m_strdup((parsing_server_index == -1) ?
		         get_server_itsname(parsing_server_index) :
		         (get_window_server(0) != -1) ?
			        get_server_itsname(get_window_server(0)) : empty_string);
}

static	char	*alias_query_nick 	_((void))
{
	char	*tmp;
	return m_strdup((tmp = query_nick()) ? tmp : empty_string);
}

static	char	*alias_target 		_((void))
{
	char	*tmp;
	return m_strdup((tmp = get_target_by_refnum(0)) ? tmp : empty_string);
}

static	char	*alias_cmdchar 		_((void))
{
	char	*cmdchars, tmp[2];

	if ((cmdchars = get_string_var(CMDCHARS_VAR)) == (char *) 0)
		cmdchars = DEFAULT_CMDCHARS;
	tmp[0] = cmdchars[0];
	tmp[1] = 0;
	return m_strdup(tmp);
}

static	char	*alias_chanop 		_((void))
{
	char	*tmp;
	return m_strdup(((tmp = get_channel_by_refnum(0)) && get_channel_oper(tmp, from_server)) ?
		"@" : empty_string);
}

static	char	*alias_modes 		_((void))
{
	char	*tmp;
	return m_strdup((tmp = get_channel_by_refnum(0)) ?
		get_channel_mode(tmp, from_server) : empty_string);
}

static  char    *alias_server_version  _((void))
{
        int s = from_server;
        if (s == -1)
        {
                if (primary_server != -1)
                        s = primary_server;
                else
                        return m_strdup(empty_string);
        }
        return m_strdup(server_list[s].version_string);
}

/*	*	*	*	*	*	*	*	*	*
		These are the built-in functions.

	About 80 of them are here, the rest are in array.c.  All of the
	stock client's functions are supported, as well as about 60 more.
	Most of the 30 stock client's functions have been re-written for
	optimization reasons, and also to further distance ircii's code
	from EPIC.
 *	*	*	*	*	*	*	*	*	*/

/* 
 * These are defined to make the construction of the built-in functions
 * easier and less prone to bugs and unexpected behaviors.  As long as
 * you consistently use these macros to do the dirty work for you, you
 * will never have to do bounds checking as the macros do that for you. >;-) 
 *
 * Yes, i realize it makes the code slightly less efficient, but i feel that 
 * the cost is minimal compared to how much time i have spent over the last 
 * year debugging these functions and the fact i wont have to again. ;-)
 */
#define EMPTY empty_string
#define RETURN_EMPTY return m_strdup(EMPTY)
#define RETURN_IF_EMPTY(x) if (empty( x )) RETURN_EMPTY
#define GET_INT_ARG(x, y) {RETURN_IF_EMPTY(y); x = atol(safe_new_next_arg(y, &y));}
#define GET_FLOAT_ARG(x, y) {RETURN_IF_EMPTY(y); x = atof(safe_new_next_arg(y, &y));}
#define GET_STR_ARG(x, y) {RETURN_IF_EMPTY(y); x = new_next_arg(y, &y);RETURN_IF_EMPTY(x);}
#define RETURN_STR(x) return m_strdup(x ? x : EMPTY)
#define RETURN_INT(x) return m_strdup(ltoa(x));

#ifdef __STDC__
#define BUILT_IN_FUNCTION(x, y) static char * x (char * y)
#else 
#define BUILT_IN_FUNCTION(x, y) static char * x ( y ) char * y; 
#endif

/*
 * Usage: $left(number text)
 * Returns: the <number> leftmost characters in <text>.
 * Example: $left(5 the quick brown frog) returns "the q"
 *
 * Note: the difference between $[10]foo and $left(10 foo) is that the former
 * is padded and the latter is not.
 */
BUILT_IN_FUNCTION(function_left, word)
{
	long	count;

	GET_INT_ARG(count, word);
	RETURN_IF_EMPTY(word);

	if (strlen(word) > count)
		word[count] = 0;

	RETURN_STR(word);
}

/*
 * Usage: $right(number text)
 * Returns: the <number> rightmost characters in <text>.
 * Example: $right(5 the quick brown frog) returns " frog"
 */
BUILT_IN_FUNCTION(function_right, word)
{
	long	count;

	GET_INT_ARG(count, word);
	RETURN_IF_EMPTY(word);

	if (strlen(word) > count)
		word += strlen(word) - count;

	RETURN_STR(word);
}

/*
 * Usage: $mid(start number text)
 * Returns: the <start>th through <start>+<number>th characters in <text>.
 * Example: $mid(3 4 the quick brown frog) returns " qui"
 *
 * Note: the first character is numbered zero.
 */
BUILT_IN_FUNCTION(function_mid, word)
{
	long	start, length;

	GET_INT_ARG(start, word);
	GET_INT_ARG(length, word);
	RETURN_IF_EMPTY(word);

	if (start < strlen(word))
	{
		word += start;
		if (length < strlen(word))
			word[length] = 0;
	}
	else
		word = EMPTY;

	RETURN_STR(word);
}

#if defined(HAVE_GETTIMEOFDAY) && !defined(OLD_RANDOM_BEHAVIOR)
static unsigned long randl _((void))
{
	struct timeval tp1;
	gettimeofday(&tp1, NULL);
	return (unsigned long) tp1.tv_usec;
}
#endif

/* patch from Sarayan to make $rand() better */

#define RAND_A 16807L
#define RAND_M 2147483647L
#define RAND_Q 127773L
#define RAND_R 2836L

#ifdef __STDC__
static	unsigned long	randm(unsigned long l)
#else
static	unsigned long	randm(l)
	unsigned long	l;
#endif
{
/* 
   If we have gettimeofday(2), we use it, because its 'leet.
	-- basically how it works is we take two samplings of 
	   gettimeofday().  Since who knows how long apart the samples
	   will be, we can be assured theyre *NOT* psudeo-random.  Then
	   we take the lower 16 bits of each sample, and then pack them
	   into a long, and viola, a random number.
	-- oh.  since we know that the usec field of timeval is microseconds,
	   the lower 16 bits change at a constant rate, which we cant say
	   for the 17th bit. >;-)
 */
#if defined(HAVE_GETTIMEOFDAY) && !defined(OLD_RANDOM_BEHAVIOR)
	unsigned long t1, t2, t;

	t1 = randl();
	t2 = randl();
	t = (t1 & 65535) * 65536 + (t2 & 65535);
	return t;
#else
	static	u_long	z = 0;
	long	t;

	if (!z)
		z = (u_long) getuid();
	if (!l)
	{
		t = RAND_A * (z % RAND_Q) - RAND_R * (z / RAND_Q);
		if (t > 0)
			z = t;
		else
			z = t + RAND_M;
		return (z >> 8) | ((z & 255) << 23);
	}
	else
	{
		if (l < 0)
			z = (u_long) getuid();
		else
			z = l;
		return 0;
	}
#endif
}

/*
 * Usage: $rand(max)
 * Returns: A random number from zero to max-1.
 * Example: $rand(10) might return any number from 0 to 9.
 */
BUILT_IN_FUNCTION(function_rand, word)
{
	long	tempin;

	GET_INT_ARG(tempin, word);
	if (tempin == 0)
		tempin = (unsigned long) -1;	/* This is cheating. :P */
	RETURN_INT(randm(0L) % tempin)
}

/*
 * Usage: $srand(seed)
 * Returns: Nothing.
 * Side effect: seeds the random number generater.
 * Note: the argument is ignored.
 */
BUILT_IN_FUNCTION(function_srand, word)
{
	randm((long) time(NULL));
	RETURN_EMPTY;
}

/*
 * Usage: $time()
 * Returns: The number of seconds that has elapsed since Jan 1, 1970, GMT.
 * Example: $time() returned something around 802835348 at the time I
 * 	    wrote this comment.
 */
BUILT_IN_FUNCTION(function_time, input)
{
	RETURN_INT(time(NULL))
}

/*
 * Usage: $stime(time)
 * Returns: The human-readable form of the date based on the <time> argument.
 * Example: $stime(1000) returns what time it was 1000 seconds from the epoch.
 * 
 * Note: $stime() is really useful when you give it the argument $time(), ala
 *       $stime($time()) is the human readable form for now.
 */
BUILT_IN_FUNCTION(function_stime, input)
{
	time_t	ltime;

	GET_INT_ARG(ltime, input);
	RETURN_STR(my_ctime(ltime));
}

/*
 * Usage: $tdiff(seconds)
 * Returns: The time that has elapsed represented in days/hours/minutes/seconds
 *          corresponding to the number of seconds passed as the argument.
 * Example: $tdiff(3663) returns "1 hour 1 minute 3 seconds"
 */
BUILT_IN_FUNCTION(function_tdiff, input)
{
	time_t	ltime;
	time_t	days,
		hours,
		minutes,
		seconds;
	char	tmp[80];
	char	*tstr;

	GET_INT_ARG(ltime, input);

	seconds = ltime % 60;
	ltime = (ltime - seconds) / 60;
	minutes = ltime % 60;
	ltime = (ltime - minutes) / 60;
	hours = ltime % 24;
	days = (ltime - hours) / 24;
	tstr = tmp;
	if (days)
	{
		sprintf(tstr, "%ld day%s ", days, plural(days));
		tstr += strlen(tstr);
	}
	if (hours)
	{
		sprintf(tstr, "%ld hour%s ", hours, plural(hours));
		tstr += strlen(tstr);
	}
	if (minutes)
	{
		sprintf(tstr, "%ld minute%s ", minutes, plural(minutes));
		tstr += strlen(tstr);
	}
	if (seconds || (!days && !hours && !minutes))
	{
		sprintf(tstr, "%ld second%s", seconds, plural(seconds));
		tstr += strlen(tstr);
	}
	else
		*--tstr = 0;	/* chop off that space! */

	RETURN_STR(tmp);
}

/*
 * Usage: $index(characters text)
 * Returns: The number of leading characters in <text> that do not occur 
 *          anywhere in the <characters> argument.
 * Example: $index(f three fine frogs) returns 6 (the 'f' in 'fine')
 *          $index(frg three fine frogs) returns 2 (the 'r' in 'three')
 */
BUILT_IN_FUNCTION(function_index, input)
{
	char	*schars;
	char	*iloc;

	GET_STR_ARG(schars, input);
	iloc = sindex(input, schars);
	RETURN_INT(iloc ? iloc - input : -1)
}

/*
 * Usage: $rindex(characters text)
 * Returns: The number of leading characters in <text> that occur before the
 *          *last* occurance of any of the characters in the <characters> 
 *          argument.
 * Example: $rindex(f three fine frogs) returns 12 (the 'f' in 'frogs')
 *          $rindex(frg three fine frogs) returns 15 (the 'g' in 'frogs')
 */
BUILT_IN_FUNCTION(function_rindex, word)
{
	char	*chars, *last;

	/* need to find out why ^x doesnt work */
	GET_STR_ARG(chars, word);
	last = rsindex(word + strlen(word) - 1, word, chars);
	RETURN_INT(last ? last - word : -1)
}

/*
 * Usage: $match(pattern list of words)
 * Returns: if no words in the list match the pattern, it returns 0.
 *	    Otherwise, it returns the number of the word that most
 *	    exactly matches the pattern (first word is numbered one)
 * Example: $match(f*bar foofum barfoo foobar) returns 3
 *	    $match(g*ant foofum barfoo foobar) returns 0
 *
 * Note: it is possible to embed spaces inside of a word or pattern simply
 *       by including the entire word or pattern in quotation marks. (")
 */
BUILT_IN_FUNCTION(function_match, input)
{
	char	*pattern, 	*word;
	long	current_match,	best_match = 0,	match = 0, match_index = 0;

	GET_STR_ARG(pattern, input);

	while (input && *input)
	{
		while (input && my_isspace(*input))
			input++;
		match_index++;
		GET_STR_ARG(word, input);
		if ((current_match = wild_match(pattern, word)) > best_match)
		{
			match = match_index;
			best_match = current_match;
		}
	}

	RETURN_INT(match)
}

/*
 * Usage: $rmatch(word list of patterns)
 * Returns: if no pattern in the list matches the word, it returns 0.
 *	    Otherwise, it returns the number of the pattern that most
 *	    exactly matches the word (first word is numbered one)
 * Example: $rmatch(foobar f*bar foo*ar g*ant) returns 2 
 *	    $rmatch(booya f*bar foo*ar g*ant) returns 0
 * 
 * Note: It is possible to embed spaces into a word or pattern simply by
 *       including the entire word or pattern within quotation marks (")
 */
BUILT_IN_FUNCTION(function_rmatch, input)
{
	char	*pattern,	*word;
	int	current_match,	best_match = 0,	match = 0, rmatch_index = 0;

	GET_STR_ARG(word, input);

	while (input && *input)
	{
		while (input && my_isspace(*input))
			input++;
		rmatch_index++;
		GET_STR_ARG(pattern, input);
		if ((current_match = wild_match(pattern, word)) > best_match)
		{
			match = rmatch_index;
			best_match = current_match;
		}
		/* WARNING WARNING HACK IN PROGRESS WARNING WARNING */
		while (input && my_isspace(*input))
			input++;
	}

	RETURN_INT(match)
}

/*
 * Usage: $userhost()
 * Returns: the userhost (if any) of the most previously recieved message.
 * Caveat: $userhost() changes with every single line that appears on
 *         your screen, so if you want to save it, you will need to assign
 *         it to a variable.
 */
BUILT_IN_FUNCTION(function_userhost, input)
{
	RETURN_STR(FromUserHost);
}

/* 
 * Usage: $strip(characters text)
 * Returns: <text> with all instances of any characters in the <characters>
 *	    argument removed.
 * Example: $strip(f free fine frogs) returns "ree ine rogs"
 *
 * Note: it can be difficult (actually, not possible) to remove spaces from
 *       a string using this function.  To remove spaces, simply use this:
 *		$tr(/ //$text)
 *
 *	 Actually, i recommend not using $strip() at all and just using
 *		$tr(/characters//$text)
 *	 (but then again, im biased. >;-)
 */
BUILT_IN_FUNCTION(function_strip, input)
{
	char	*result;
	char	*chars;
	char	*cp, *dp;

	GET_STR_ARG(chars, input);
	RETURN_IF_EMPTY(input);

	result = (char *)new_malloc(strlen(input) + 1);
	for (cp = input, dp = result; *cp; cp++)
	{
		/* This is expensive -- gotta be a better way */
		if (!index(chars, *cp))
			*dp++ = *cp;
	}
	*dp = '\0';

	return result;		/* DONT USE RETURN_STR HERE! */
}

/*
 * Usage: $encode(text)
 * Returns: a string, uniquely identified with <text> such that the string
 *          can be used as a variable name.
 * Example: $encode(fe fi fo fum) returns "GGGFCAGGGJCAGGGPCAGGHFGN"
 *
 * Note: $encode($decode(text)) returns text (most of the time)
 *       $decode($encode(text)) also returns text.
 */
#ifdef __STDC__
static char	*function_encode (unsigned char *input)
#else
static char	* function_encode(input)
	unsigned char	*input;
#endif
{
	char	*result;
	int	i = 0;

	result = (char *)new_malloc(strlen(input) * 2 + 1);
	while (*input)
	{
		result[i++] = (*input >> 4) + 0x41;
		result[i++] = (*input & 0x0f) + 0x41;
		input++;
	}
	result[i] = '\0';

	return result;		/* DONT USE RETURN_STR HERE! */
}


/*
 * Usage: $decode(text)
 * Returns: If <text> was generated with $encode(), it returns the string
 *          you originally encoded.  If it wasnt, you will probably get
 *	    nothing useful in particular.
 * Example: $decode(GGGFCAGGGJCAGGGPCAGGHFGN) returns "fe fi fo fum"
 *
 * Note: $encode($decode(text)) returns "text"
 *       $decode($encode(text)) returns "text" too.
 *
 * Note: Yes.  $decode(plain-text) does compress the data by a factor of 2.
 *       But it ignores non-ascii text, so use this as compression at your
 *	 own risk and peril.
 */
#ifdef __STDC__
static char	*function_decode (unsigned char *input)
#else
static char	* function_decode(input)
	unsigned char	*input;
#endif
{
	char	*result;
	int	i = 0;

	result = (char *)new_malloc(strlen(input) / 2 + 1);

	while (input[0] && input[1])
	{
		/* oops, this isnt quite right. */
		result[i] = ((input[0] - 0x41) << 4) | (input[1] - 0x41);
		input += 2;
		i++;
	}
	result[i] = '\0';

	return result;		/* DONT USE RETURN_STR HERE! */
}


/*
 * Usage: $ischannel(text)
 * Returns: If <text> could be a valid channel name, 1 is returned.
 *          If <text> is an illegal channel name, 0 is returned.
 *
 * Note: Contrary to popular belief, this function does NOT determine
 * whether a given channel name is in use!
 */
BUILT_IN_FUNCTION(function_ischannel, input)
{
	RETURN_INT(is_channel(input))
}

/*
 * Usage: $ischanop(nick channel)
 * Returns: 1 if <nick> is a channel operator on <channel>
 *          0 if <nick> is not a channel operator on <channel>
 *			* O R *
 *	      if you are not on <channel>
 *
 * Note: Contrary to popular belief, this function can only tell you
 *       who the channel operators are for channels you are already on!
 *
 * Boo Hiss:  This should be $ischanop(channel nick <nick...nick>)
 *	      and return a list (1 1 ... 0), which would allow us to
 *	      call is_chanop() without ripping off the nick, and allow 
 *	      us to abstract is_chanop() to take a list. oh well... 
 *	      Too late to change it now. :/
 */
BUILT_IN_FUNCTION(function_ischanop, input)
{
	char	*nick;

	GET_STR_ARG(nick, input);
	RETURN_INT(is_chanop(input, nick))
}


/*
 * Usage: $word(number text)
 * Returns: the <number>th word in <text>.  The first word is numbered zero.
 * Example: $word(3 one two three four five) returns "four" (think about it)
 */
BUILT_IN_FUNCTION(function_word, word)
{
	int	cvalue;
	char	*w_word;

	GET_INT_ARG(cvalue, word);
	if (cvalue < 0)
		RETURN_EMPTY;

	while (cvalue-- > 0)
		GET_STR_ARG(w_word, word);

	GET_STR_ARG(w_word, word);
	RETURN_STR(w_word);
}


/*
 * Usage: $winnum()
 * Returns: the index number for the current window
 * 
 * Note: returns -1 if there are no windows open (ie, in dumb mode)
 */
BUILT_IN_FUNCTION(function_winnum, input)
{
	RETURN_INT(curr_scr_win ? curr_scr_win->refnum : -1)
}

BUILT_IN_FUNCTION(function_winnam, input)
{
	if (curr_scr_win)
		RETURN_STR(curr_scr_win->name);
	else
		RETURN_EMPTY;
}

BUILT_IN_FUNCTION(function_connect, input)
{
	char	*host;
	int	port;

	GET_STR_ARG(host, input);
	GET_INT_ARG(port, input);

	return dcc_raw_connect(host, port);	/* DONT USE RETURN_STR HERE! */
}


BUILT_IN_FUNCTION(function_listen, input)
{
	int	port = 0;
	char	*result;

	/* Oops. found by CrowMan, listen() has a default. erf. */
	if (input && *input)
	{
		char *tmp, *ptr;
		if ((tmp = new_next_arg(input, &input)))
		{
			port = strtoul(tmp, &ptr, 10);
			if (ptr == tmp)
				RETURN_EMPTY;	/* error. */
		}
	}

	result = dcc_raw_listen(port);
	RETURN_STR(result);			/* DONT REMOVE RESULT! */
}

BUILT_IN_FUNCTION(function_toupper, input)
{
	return (upper(m_strdup(input)));
}

BUILT_IN_FUNCTION(function_tolower, input)
{
	return (lower(m_strdup(input)));
}

BUILT_IN_FUNCTION(function_curpos, input)
{
	RETURN_INT(current_screen->buffer_pos)
}

BUILT_IN_FUNCTION(function_channels, input)
{
	long	winnum;
	Window  *window = curr_scr_win;

	if (isdigit(*input))
	{
		GET_INT_ARG(winnum, input);
		window = get_window_by_refnum(winnum);
	}

	return create_channel_list(window);	/* DONT USE RETURN_STR HERE! */
}

BUILT_IN_FUNCTION(function_servers, input)
{
	return create_server_list();		/* DONT USE RETURN_STR HERE! */
}

BUILT_IN_FUNCTION(function_pid, input)
{
	RETURN_INT(getpid())
}

BUILT_IN_FUNCTION(function_ppid, input)
{
	RETURN_INT(getppid())
}


/*
 * strftime() patch from hari (markc@arbld.unimelb.edu.au)
 */
BUILT_IN_FUNCTION(function_strftime, input)
{
	char		result[128];
	time_t		ltime;
	struct tm	*tm;

	if (isdigit(*input))
		ltime = strtoul(input, &input, 10);
	else
		ltime = time(NULL);

	while (*input && my_isspace(*input))
		++input; 

	if (!*input)
		return m_strdup(empty_string);


	tm = localtime(&ltime);

	if (!strftime(result, 128, input, tm))
		return m_strdup(empty_string);

	return m_strdup(result);
}

BUILT_IN_FUNCTION(function_idle, input)
{
	return alias_idle();
}



/* The new "added" functions */

/* $before(chars string of text)
 * returns the part of "string of text" that occurs before the
 * first instance of any character in "chars"
 * EX:  $before(! hop!jnelson@iastate.edu) returns "hop"
 */
BUILT_IN_FUNCTION(function_before, word)
{
	char	*pointer = (char *) 0;
	char	*chars;
	char	*tmp;
	long	numint;

	GET_STR_ARG(tmp, word);			/* DONT DELETE TMP! */
	numint = atol(tmp);

	if (numint)
	{
		GET_STR_ARG(chars, word);
	}
	else
	{
		numint = 1;
		chars = tmp;
	}

	if (numint < 0)
		pointer = word + strlen(word) - 1;

	pointer = search(word, &pointer, chars, numint);

	if (!pointer)
		RETURN_EMPTY;

	*pointer = '\0';
	RETURN_STR(word);
}

/* $after(chars string of text)
 * returns the part of "string of text" that occurs after the 
 * first instance of any character in "chars"
 * EX: $after(! hop!jnelson@iastate.edu)  returns "jnelson@iastate.edu"
 */
BUILT_IN_FUNCTION(function_after, word)
{
	char	*chars;
	char	*pointer = (char *) 0;
	char 	*tmp;
	long	numint;

	GET_STR_ARG(tmp, word);
	numint = atol(tmp);

	if (numint)
		chars = new_next_arg(word, &word);
	else
	{
		numint = 1;
		chars = tmp;
	}

	if (numint < 0)
		pointer = word + strlen(word) - 1;

	pointer = search(word, &pointer, chars, numint);

	if (!pointer)
		RETURN_EMPTY;

	RETURN_STR(pointer + 1);
}

/* $leftw(num string of text)
 * returns the left "num" words in "string of text"
 * EX: $leftw(3 now is the time for) returns "now is the"
 */
BUILT_IN_FUNCTION(function_leftw, word)
{
	int value;
 
	GET_INT_ARG(value, word);
	if (value < 1)
		RETURN_EMPTY;

	return (extract(word, 0, value-1));	/* DONT USE RETURN_STR HERE! */
}

/* $rightw(num string of text)
 * returns the right num words in "string of text"
 * EX: $rightw(3 now is the time for) returns "the time for"
 */
BUILT_IN_FUNCTION(function_rightw, word)
{
	int     value;

	GET_INT_ARG(value, word);
	if (value < 1)
		RETURN_EMPTY;
		
	return extract2(word, -value, EOS); 
}


/* $midw(start num string of text)
 * returns "num" words starting at word "start" in the string "string of text"
 * NOTE: The first word is word #0.
 * EX: $midw(2 2 now is the time for) returns "the time"
 */
BUILT_IN_FUNCTION(function_midw, word)
{
	int     start, num;

	GET_INT_ARG(start, word);
	GET_INT_ARG(num, word);

	if (num < 1)
		RETURN_EMPTY;

	return extract(word, start, (start + num - 1));
}

/* $notw(num string of text)
 * returns "string of text" with word number "num" removed.
 * NOTE: The first word is numbered 0.
 * EX: $notw(3 now is the time for) returns "now is the for"
 */
BUILT_IN_FUNCTION(function_notw, word)
{
	char    *booya = (char *)0;
	int     where;
	
	GET_INT_ARG(where, word);

	/* An illegal word simply returns the string as-is */
	if (where < 0)
		RETURN_STR(word);

	if (where > 0)
	{
		char *part1, *part2;
		part1 = extract(word, 0, (where - 1));
		part2 = extract(word, (where + 1), EOS);
		booya = m_strdup(part1);
		/* if part2 is there, append it. */
		m_s3cat_s(&booya, space, part2);
		new_free(&part1);
		new_free(&part2);
	}
	else /* where == 0 */
		booya = extract(word, 1, EOS);

	return booya;				/* DONT USE RETURN_STR HERE! */
}

/* $restw(num string of text)
 * returns "string of text" that occurs starting with and including
 * word number "num"
 * NOTE: the first word is numbered 0.
 * EX: $restw(3 now is the time for) returns "time for"
 */
BUILT_IN_FUNCTION(function_restw, word)
{
	int     where;
	
	GET_INT_ARG(where, word);
	if (where < 0)
		RETURN_EMPTY;
	return extract(word, where, EOS);
}

/* $remw(word string of text)
 * returns "string of text" with the word "word" removed
 * EX: $remw(the now is the time for) returns "now is time for"
 */
BUILT_IN_FUNCTION(function_remw, word)
{
	char    *booya = (char *) 0;
	char    *blah = (char *) 0;
	char    *lame = (char *) 0, *plame;
	int	where;

	lame = m_strdup(word);
	plame = lame;
	blah = function_rmatch(word);
	where = atoi(blah);

	/* No matches, return the string as-is */
	if (where == 0)
	{
		new_next_arg(lame, &lame);
		booya = m_strdup(lame);
	}
	else
	{
		char *str1, *str2;
		str1 = extract(lame, 1, (where - 1));
		str2 = extract(lame, (where + 1), EOS);
		booya = m_strdup(str1);
		/* if str2 is there, append it */
		m_s3cat_s(&booya, space, str2);
		new_free(&str1);
		new_free(&str2);
	}
	new_free(&blah);
	new_free(&plame);
	return booya;				/* DONT USE RETURN_STR HERE! */
}

/* $insertw(num word string of text)
 * returns "string of text" such that "word" is the "num"th word
 * in the string.
 * NOTE: the first word is numbered 0.
 * EX: $insertw(3 foo now is the time for) returns "now is the foo time for"
 */
BUILT_IN_FUNCTION(function_insertw, word)
{
	int     where;
	char    *what;
	char    *booya=(char *)0;
	char 	*str1, *str2;

	GET_INT_ARG(where, word);
	
	/* If the word goes at the front of the string, then it
	   already is: return it. ;-) */
	if (where < 1)
		booya = m_strdup(word);
	else
	{
		GET_STR_ARG(what, word);
		str1 = extract(word, 0, (where - 1));
		str2 = extract(word, where, EOS);
		if (str1 && *str1)
			booya = m_2dup(str1, space);
		malloc_strcat(&booya, what);
		m_s3cat_s(&booya, space, str2);
		new_free(&str1);
		new_free(&str2);
	}

	return booya;				/* DONT USE RETURN_STR HERE! */
}

/* $chngw(num word string of text)
 * returns "string of text" such that the "num"th word is removed 
 * and replaced by "word"
 * NOTE: the first word is numbered 0
 * EX: $chngw(3 foo now is the time for) returns "now is the foo for"
 */
BUILT_IN_FUNCTION(function_chngw, word)
{
	int     which;
	char    *what;
	char    *booya=(char *)0;
	char	*str1, *str2;
	
	GET_INT_ARG(which, word);
	GET_STR_ARG(what, word);

	if (which < 0)
		RETURN_STR(word);

	/* hmmm. if which is 0, extract does the wrong thing. */
	str1 = extract(word, 0, which - 1);
	str2 = extract(word, which + 1, EOS);
	if (str1 && *str1)
		booya = m_2dup(str1, space);
	malloc_strcat(&booya, what);
	m_s3cat_s(&booya, space, str2);
	new_free(&str1);
	new_free(&str2);

	return (booya);
}


/* $common (string of text / string of text)
 * Given two sets of words seperated by a forward-slash '/', returns
 * all words that are found in both sets.
 * EX: $common(one two three / buckle my two shoe one) returns "one two"
 * NOTE: returned in order found in first string.
 * NOTE: This may fudge if you have a word in the first set for which
 *       there is a word in the second set that is a superset of the word:
 *	 $common(one two three / phone ooga booga) returns "one"
 */
BUILT_IN_FUNCTION(function_common, word)
{
	char    *left = (char *) 0, **leftw = NULL,
		*right = (char *) 0, **rightw = NULL, *booya = NULL;
	int	leftc, lefti,
		rightc, righti;

	left = word;
	if ((right = index(word,'/')) == NULL)
		RETURN_EMPTY;

	*right++ = 0;
	leftc = splitw(left, &leftw);
	rightc = splitw(right, &rightw);

	for (lefti = 0; lefti < leftc; lefti++)
	{
		for (righti = 0; righti < rightc; righti++)
		{
			if (rightw[righti] && !my_stricmp(leftw[lefti], rightw[righti]))
			{
				m_s3cat(&booya, space, leftw[lefti]);
				rightw[righti] = NULL;
			}
		}
	}

	new_free((char **)&leftw);
	new_free((char **)&rightw);

	if (!booya)
		RETURN_EMPTY;

	return (booya);				/* DONT USE RETURN_STR HERE! */
}

/* $diff(string of text / string of text)
 * given two sets of words, seperated by a forward-slash '/', returns
 * all words that are not found in both sets
 * EX: $diff(one two three / buckle my two shoe)
 * returns "one two three buckle my shoe"
 */
BUILT_IN_FUNCTION(function_diff, word)
{
	char 	*left = NULL, **leftw = NULL,
	     	*right = NULL, **rightw = NULL, *booya = NULL;
	int 	lefti, leftc,
	    	righti, rightc;
	int 	found;

	left = word;
	if ((right = index(word,'/')) == (char *) 0)
		RETURN_EMPTY;

	*right++ = 0;
	leftc = splitw(left, &leftw);
	rightc = splitw(right, &rightw);

	for (lefti = 0; lefti < leftc; lefti++)
	{
		found = 0;
		for (righti = 0; righti < rightc; righti++)
		{
			if (rightw[righti] && !my_stricmp(leftw[lefti], rightw[righti]))
			{
				found = 1;
				rightw[righti] = NULL;
			}
		}
		if (!found)
			m_s3cat(&booya, space, leftw[lefti]);
	}

	for (righti = 0; righti < rightc; righti++)
	{
		if (rightw[righti])
			m_s3cat(&booya, space, rightw[righti]);
	}

	new_free((char **)&leftw);
	new_free((char **)&rightw);

	if (!booya)
		RETURN_EMPTY;

	return (booya);
}

/* $pattern(pattern string of words)
 * given a pattern and a string of words, returns all words that
 * are matched by the pattern
 * EX: $pattern(f* one two three four five) returns "four five"
 */
BUILT_IN_FUNCTION(function_pattern, word)
{
	char    *blah;
	char    *booya = m_strdup(empty_string);
	char    *pattern;

	if ((pattern = new_next_arg(word, &word)))
	{
		while (((blah = new_next_arg(word, &word)) != NULL))
		{
			if (wild_match(pattern, blah))
				m_s3cat(&booya, space, blah);
		}
		return (booya);
	} 
	else 
		return m_strdup(empty_string);	
}

/* $filter(pattern string of words)
 * given a pattern and a string of words, returns all words that are 
 * NOT matched by the pattern
 * $filter(f* one two three four five) returns "one two three"
 */
BUILT_IN_FUNCTION(function_filter, word)
{
	char    *blah;
	char    *booya = m_strdup(empty_string);
	char    *pattern;

	if ((pattern = new_next_arg(word, &word)))
	{
		while ((blah = new_next_arg(word, &word)) != NULL)
		{
			if (!wild_match(pattern, blah))
				m_s3cat(&booya, space, blah);
		}
		return (booya);
	} 
	else 
		return m_strdup(empty_string);
}

/* $rpattern(word list of patterns)
 * Given a word and a list of patterns, return all patterns that
 * match the word.
 * EX: $rpattern(jnelson@iastate.edu *@* jnelson@* f*@*.edu)
 * returns "*@* jnelson@*"
 */
BUILT_IN_FUNCTION(function_rpattern, word)
{
	char    *blah;
	char    *booya = m_strdup(empty_string);
	char    *pattern;

	if ((blah = new_next_arg(word, &word)))
	{
		while ((pattern = new_next_arg(word, &word)) != NULL)
		{
			if (wild_match(pattern, blah))
				m_s3cat(&booya, space, pattern);
		}
		return (booya);
	} 
	else 
		return m_strdup(empty_string);
}

/* $rfilter(word list of patterns)
 * given a word and a list of patterns, return all patterns that
 * do NOT match the word
 * EX: $rfilter(jnelson@iastate.edu *@* jnelson@* f*@*.edu)
 * returns "f*@*.edu"
 */
BUILT_IN_FUNCTION(function_rfilter, word)
{
	char    *blah;
	char    *booya = m_strdup(empty_string);
	char    *pattern;

	if ((blah = new_next_arg(word, &word)))
	{
		while ((pattern = new_next_arg(word, &word)) != NULL)
		{
			if (!wild_match(pattern, blah))
				m_s3cat(&booya, space, pattern);
		}
		return (booya);
	} 
	else 
		return m_strdup(empty_string);
}

/* $copattern(pattern var_1 var_2)
 * Given a pattern and two variable names, it returns all words
 * in the variable_2 corresponding to any words in variable_1 that
 * are matched by the pattern
 * EX: @nicks = [hop IRSMan skip]
 *     @userh = [jnelson@iastate.edu irsman@iastate.edu sanders@rush.cc.edu]
 *     $copattern(*@iastate.edu userh nicks) 
 *	returns "hop IRSMan"
 */
BUILT_IN_FUNCTION(function_copattern, word)
{
	char	*booya = (char *) 0,
		*pattern = (char *) 0,
		*firstl = (char *) 0, *firstlist = (char *) 0, *firstel = (char *) 0,
		*secondl = (char *) 0, *secondlist = (char *) 0, *secondel = (char *) 0;
	char 	*sfirstl, *ssecondl;

	GET_STR_ARG(pattern, word);
	GET_STR_ARG(firstlist, word);
	GET_STR_ARG(secondlist, word);

	firstl = find_inline(firstlist);
	secondl = find_inline(secondlist);
	sfirstl = firstl;
	ssecondl = secondl;

	while ((firstel = new_next_arg(firstl, &firstl)))
	{
		if (!(secondel = new_next_arg(secondl, &secondl)))
		{
			(void) (booya || (booya = m_strdup(empty_string)));
			break;
		}

		if (wild_match(pattern, firstel))
			m_s3cat(&booya, space, secondel);
	}
	new_free(&sfirstl);
	new_free(&ssecondl);

	if (!booya) 
		RETURN_EMPTY;

	return (booya);
}

/* $beforew(pattern string of words)
 * returns the portion of "string of words" that occurs before the 
 * first word that is matched by "pattern"
 * EX: $beforew(three one two three o leary) returns "one two"
 */
BUILT_IN_FUNCTION(function_beforew, word)
{
	int     where;
	char	*lame = (char *) 0;
	char	*placeholder;

	lame = m_strdup(word);
	placeholder = function_rmatch(word);
	where = atoi(placeholder);

	new_free(&placeholder);

	if (where < 1)
	{
		new_free(&lame);
		RETURN_EMPTY;
	}
	placeholder = extract(lame, 1, where - 1);
	new_free(&lame);
	return placeholder;
}
		
/* Same as above, but includes the word being matched */
BUILT_IN_FUNCTION(function_tow, word)
{
	int     where;
	char	*lame = (char *) 0;
	char	*placeholder;

	lame = m_strdup(word);
	placeholder = function_rmatch(word);
	where = atoi(placeholder);

	new_free(&placeholder);

	if (where < 1)
	{
		new_free(&lame);
		RETURN_EMPTY;
	}
	placeholder = extract(lame, 1, where);
	new_free(&lame);
	return placeholder;
}

/* Returns the string after the word being matched */
BUILT_IN_FUNCTION(function_afterw, word)
{
	int     where;
	char	*lame = (char *) 0;
	char	*placeholder;

	lame = m_strdup(word);
	placeholder = function_rmatch(word);
	where = atoi(placeholder);

	new_free(&placeholder);

	if (where < 1)
	{
		new_free(&lame);
		RETURN_EMPTY;
	}
	placeholder = extract(lame, where + 1, EOS);
	new_free(&lame);
	return placeholder;
}

/* Returns the string starting with the word being matched */
BUILT_IN_FUNCTION(function_fromw, word)
{
	int     where;
	char 	*lame = (char *) 0;
	char	*placeholder;

	lame = m_strdup(word);
	placeholder = function_rmatch(word);
	where = atoi(placeholder);

	new_free(&placeholder);

	if (where < 1)
	{
		new_free(&lame);
		RETURN_EMPTY;
	}

	placeholder = extract(lame, where, EOS);
	new_free(&lame);
	return placeholder;
}

/* Cut and paste a string */
BUILT_IN_FUNCTION(function_splice, word)
{
	char    *variable;
	int	start;
	int	length;
	char 	*left_part = NULL;
	char	*middle_part = NULL;
	char	*right_part = NULL;
	char	*old_value = NULL;
	char	*new_value = NULL;
	int 	old_display = window_display;
	int	num_words;

	GET_STR_ARG(variable, word);
	GET_INT_ARG(start, word);
	GET_INT_ARG(length, word);

	old_value = find_inline(variable);
	num_words = word_count(old_value);

	if (start < 0)
	{
		if ((length += start) <= 0)
			RETURN_EMPTY;
		start = 0;
	}

	if (start >= num_words)
	{
		left_part = m_strdup(old_value);
		middle_part = m_strdup(empty_string);
		right_part = m_strdup(empty_string);
	}

	else if (start + length >= num_words)
	{
		left_part = extract(old_value, 0, start - 1);
		middle_part = extract(old_value, start, EOS);
		right_part = m_strdup(empty_string);
	}

	else
	{
		left_part = extract(old_value, 0, start - 1);
		middle_part = extract(old_value, start, start + length - 1);
		right_part = extract(old_value, start + length, EOS);
	}

	new_value = NULL;
	malloc_strcpy(&new_value, left_part);
	if (new_value && *new_value && word && *word)
		malloc_strcat(&new_value, space);
	if (word && *word)
		malloc_strcat(&new_value, word);
	if (new_value && *new_value && *right_part && *right_part)
		malloc_strcat(&new_value, space);
	if (right_part && *right_part)
		malloc_strcat(&new_value, right_part);

	window_display = 0;
	add_alias(VAR_ALIAS, variable, new_value);
	window_display = old_display;

	new_free(&old_value);
	new_free(&new_value);
	new_free(&left_part);
	new_free(&right_part);
	return middle_part;
}

BUILT_IN_FUNCTION(function_numonchannel, word)
{
	char            channel[255];
	ChannelList	*chan = (ChannelList *) 0;
	NickList	*tmp = (NickList *) 0;
	int             counter = 0;
	
	if (word && *word)
		strmcpy(channel, new_next_arg(word, &word), 254);
	else
		strmcpy(channel, empty_string, 254);

	if ((channel == (char *) 0) || (!is_channel(channel)) ||
	    (!(chan = lookup_channel(channel, curr_scr_win->server,0))))
		RETURN_INT(0)

	for (tmp = chan->nicks; tmp; tmp = tmp->next)
		counter++;

	RETURN_INT(counter)
}
	
BUILT_IN_FUNCTION(function_onchannel, word)
{
	char		channel[255];	/* max channel name size */
	ChannelList     *chan = (ChannelList *) 0;
	NickList        *tmp = (NickList *) 0;
	char            *nicks = (char *) 0;
	char	*curr_chan;

	if (word && *word)
		strmcpy(channel, new_next_arg(word, &word), 254);
	else
		strmcpy(channel, empty_string, 254);

	curr_chan = get_channel_by_refnum(0);
	if (!curr_chan)
		curr_chan = empty_string;

	if ((*channel == 0) || (!strcmp(channel, "*")))
		strmcpy(channel, curr_chan, 254);

	if (*channel == 0 || *channel == '0')
		RETURN_EMPTY;

	if (!is_channel(channel))   /* must be a new onchannel */
	{
		char nick[255];

		strmcpy(nick, channel, 254);
		if (word && *word)
			strmcpy(channel, new_next_arg(word, &word), 254);
		else
			strmcpy(channel, curr_chan, 254);

		if (*channel)
			RETURN_INT(is_on_channel(channel, nick) ? 1 : 0)
		else
			RETURN_INT(0)
	}

	chan = lookup_channel(channel,curr_scr_win->server,0);
	if (chan == (ChannelList *) 0)
		RETURN_EMPTY;

	for (tmp = chan->nicks; tmp; tmp = tmp->next)
		m_s3cat(&nicks, space, tmp->nick);

	if (!nicks)
		RETURN_EMPTY;

	return(nicks);
}

BUILT_IN_FUNCTION(function_chops, word)
{
	char		channel[255];	/* maximum size of channel name */
	ChannelList     *chan;
	NickList        *tmp;
	char            *nicks = (char *) 0;
	char 	*curr_chan;

	if (word && *word)
		strmcpy(channel, new_next_arg(word, &word), 254);
	else
		strmcpy(channel, empty_string, 254);

	curr_chan = get_channel_by_refnum (0);
	if (!curr_chan)
		curr_chan = empty_string;

	if ((*channel == 0) || (!strcmp(channel, "*")))
		strmcpy(channel, curr_chan, 254);

	if (*channel == 0 || *channel == '0')
		RETURN_EMPTY;

	chan = lookup_channel(channel,curr_scr_win->server,0);
	if (!chan)
		RETURN_EMPTY;

	for (tmp = chan->nicks; tmp; tmp = tmp->next)
	{
		if (tmp->chanop == 1) 
			m_s3cat(&nicks, space, tmp->nick);
	}
	if (!nicks)
		RETURN_EMPTY;

	return(nicks);
}        

BUILT_IN_FUNCTION(function_nochops, word)
{
	ChannelList     *chan;
	NickList        *tmp;
	char            *nicks = (char *)0;
	char            channel[255];
	char	*curr_chan;

	if (word && *word)
		strmcpy(channel, new_next_arg(word, &word), 254);
	else
		strmcpy(channel, empty_string, 254);

	curr_chan = get_channel_by_refnum(0);
	if (!curr_chan)
		curr_chan = empty_string;

	if ((*channel == 0) || (!strcmp(channel, "*")))
		strmcpy(channel, curr_chan, 254);

	if (*channel == 0 || *channel == '0')
		RETURN_EMPTY;

	chan = lookup_channel(channel,curr_scr_win->server,0);
	if (!chan)
		RETURN_EMPTY;

	for (tmp = chan->nicks; tmp; tmp = tmp->next)
	{
		if (tmp->chanop == 0) 
			m_s3cat(&nicks, space, tmp->nick);
	}
	if (!nicks)
		RETURN_EMPTY;

	return(nicks);
}

BUILT_IN_FUNCTION(function_key, word)
{
	char	channel[257];
	char    *booya = (char *) 0;
	int debug = 0;
	char *curr_chan;

	if (word && *word)
		strmcpy(channel, new_next_arg(word, &word), 254);
	else
		strmcpy(channel, empty_string, 254);

	curr_chan = get_channel_by_refnum(0);
	if (!curr_chan)
		curr_chan = empty_string;

	for (;;)
	{
		char    *key;

		if (debug++ > 100)
			panic("function_key: infinite loop");

		/* If we are at the end of the list */
		if (!*channel)
		{
			/* If there were other channels, stop */
			if (booya)
				break;

			/* Empty list -- use current channel */
			else
				strmcpy(channel, curr_chan, 256);
		}

		/* Not at end of list, look to see if channel is "*" */
		else if (!strcmp(channel, "*"))
			strmcpy(channel, curr_chan, 256);

		/* If there is still no channel, stop */
		if (!*channel)
			return (booya ? booya : m_strdup(empty_string));

		if ((key = channel_key(channel)))
			m_s3cat(&booya, space, (key || *key) ? key : "*");

		if (!word || !*word)
			break;

		strmcpy(channel, new_next_arg(word, &word), 256);
	}

	if (!booya)
		RETURN_EMPTY;

	return (booya);
}

BUILT_IN_FUNCTION(function_revw, words)
{
	char *booya = NULL;

	while (words && *words)
		m_s3cat(&booya, space, last_arg(&words));

	if (!booya)
		RETURN_EMPTY;

	return booya;
}

BUILT_IN_FUNCTION(function_reverse, words)
{
	int     length = strlen(words);
	char    *booya = (char *) 0;
	int     x = 0;

	booya = (char *)new_malloc(length+1);
	for(length--; length >= 0; length--,x++)
		booya[x] = words[length];
	booya[x]='\0';
	return (booya);
}

BUILT_IN_FUNCTION(function_jot, input)
{
	int     start = 0;
	int     stop = 0;
	int     interval = 1;
	int     counter;
	char	*booya = NULL;

        GET_INT_ARG(start,input)
        GET_INT_ARG(stop, input)
        if (input && *input)
                GET_INT_ARG(interval, input)
        else
                interval = 1;

        if (interval < 0) 
                interval = -interval;

        if (start < stop)
                for (counter = start;counter <= stop;counter+=interval)
			m_s3cat(&booya, space, ltoa(counter));
        else
                for (counter = start;counter >= stop;counter-=interval)
			m_s3cat(&booya, space, ltoa(counter));


	return booya;
}

#ifdef __STDC__
extern char *function_shift (char *word)
#else
extern char *function_shift (word) char *word;
#endif
{
	char    *value = (char *) 0;
	char    *var    = (char *) 0;
	char	*booya 	= (char *) 0;
	int     old_display = window_display;
	char    *placeholder;

	GET_STR_ARG(var, word);

	if (word && *word)
		RETURN_STR(var);

	value = find_inline(var);

	if (!value && !*value)
		RETURN_EMPTY;

	placeholder = value;
	booya = m_strdup(new_next_arg(value, &value));
	if (var)
	{
		window_display = 0;
		add_alias(VAR_ALIAS, var, value);
		window_display = old_display;
	}
	new_free(&placeholder);
	if (!booya)
		RETURN_EMPTY;
	return booya;
}

#ifdef __STDC__
extern char *function_unshift (char *word)
#else
extern char *function_unshift (word) char *word;
#endif
{
	char    *value = (char *) 0;
	char    *var    = (char *) 0;
	char	*booya  = (char *) 0;
	int     old_display = window_display;

	GET_STR_ARG(var, word);
	value = find_inline(var);
	if (!word || !*word)
		return value;

	booya = m_strdup(word);
	m_s3cat_s(&booya, space, value);

	window_display = 0;
	add_alias(VAR_ALIAS, var, booya);
	window_display = old_display;
	new_free(&value);
	return booya;
}

#ifdef __STDC__
extern char *function_push (char *word)
#else
extern char *function_push (word) char *word;
#endif
{
	char    *value = (char *) 0;
	char    *var    = (char *) 0;
	int     old_display = window_display;

	GET_STR_ARG(var, word);
	value = find_inline(var);
	m_s3cat(&value, space, word);
	window_display = 0;
	add_alias(VAR_ALIAS, var, value);
	window_display = old_display;
	return value;
}

#ifdef __STDC__
extern char *function_pop (char *word)
#else
extern char *function_pop (word) char *word;
#endif
{
	char *value	= (char *) 0;
	char *var	= (char *) 0;
	char *pointer	= (char *) 0;
	int   old_display = window_display;
	char *blech     = (char *) 0;

	GET_STR_ARG(var, word);

	if (word && *word)
	{
		pointer = rindex(word, ' ');
		RETURN_STR(pointer ? pointer : word);
	}

	value = find_inline(var);
	if (!value || !*value)
	{
		new_free(&value);
		RETURN_EMPTY;
	}

	if (!(pointer = rindex(value, ' ')))
	{
		window_display = 0;
		add_alias(VAR_ALIAS, var, empty_string); /* dont forget this! */
		window_display = old_display;
		return value;	/* one word -- return it */
	}

	*pointer++ = '\0';
	window_display = 0;
	add_alias(VAR_ALIAS, var, value);
	window_display = old_display;

	/* because pointer points to value, we *must* make a copy of it
	 * *before* we free value! (And we cant forget to free value, either) */
	blech = m_strdup(pointer);
	new_free(&value);
	return blech;
}


/* Search and replace function --
   Usage:   $sar(c/search/replace/data)
   Commands:
		r - treat data as a variable name and 
		    return the replaced data to the variable
		g - Replace all instances, not just the first one
   The delimiter may be any character that is not a command (typically /)
   The delimiter MUST be the first character after the command
   Returns empty string on error
*/
BUILT_IN_FUNCTION(function_sar, word)
{
	char    delimiter;
	char	*pointer	= (char *) 0;
	char    *search         = (char *) 0;
	char    *replace        = (char *) 0;
	char    *data		= (char *) 0;
	char	*value		= (char *) 0;
	char	*booya		= (char *) 0;
	int	variable = 0,global = 0,searchlen,oldwindow = window_display;
	char 	*svalue;

	while (((*word == 'r') && (variable = 1)) || ((*word == 'g') && (global = 1)))
		word++;

	RETURN_IF_EMPTY(word);

	delimiter = *word;
	search = word + 1;
	if ((replace = index(search, delimiter)) == 0)
		RETURN_EMPTY;

	*replace++ = 0;
	if ((data = index(replace,delimiter)) == 0)
		RETURN_EMPTY;

	*data++ = '\0';

	value = (variable == 1) ? find_inline(data) : m_strdup(data);

	if (!value || !*value)
	{
		if (value)
			new_free(&value);
		RETURN_EMPTY;
	}
	svalue = value;

	pointer = value;
	searchlen = strlen(search) - 1;

	if (global)
	{
		while ((pointer = strstr(pointer,search)) != (char *) 0)
		{
			pointer[0] = pointer[searchlen] = 0;
			pointer += searchlen + 1;
			m_e3cat(&booya, value, replace);
			value = pointer;
		}
	} 
	else
	{
		if ((pointer = strstr(pointer,search)) != (char *) 0)
		{
			pointer[0] = pointer[searchlen] = 0;
			pointer += searchlen + 1;
			m_e3cat(&booya, value, replace);
			value = pointer;
		}
	}

	malloc_strcat(&booya, value);
	if (variable) 
	{
		window_display = 0;
		add_alias(VAR_ALIAS, data, booya);
		window_display = oldwindow;
	}
	new_free(&svalue);
	return (booya);
}

BUILT_IN_FUNCTION(function_center, word)
{
	int	length,pad,width;
	char 	*padc;

	if (!word || !*word)
		RETURN_EMPTY;

	width = atoi(new_next_arg(word, &word));
	length = strlen(word);
	
	if ((pad = width - length) < 0)
		RETURN_STR(word);

	pad /= 2;
	padc = (char *)new_malloc(width+1);
	memset(padc, ' ', pad);
	padc[pad] = '\0';

	return strcat(padc, word);
}

BUILT_IN_FUNCTION(function_split, word)
{
	char	*chrs;
	char	*pointer;

	chrs = next_arg(word, &word);
	pointer = word;
	while ((pointer = sindex(pointer,chrs)))
		*pointer++ = ' ';

	RETURN_STR(word);
}

BUILT_IN_FUNCTION(function_chr, word)
{
	char aboo[BIG_BUFFER_SIZE];
	char *ack = aboo;
	char *blah;

	while ((blah = next_arg(word, &word)))
		*ack++ = (char)atoi(blah);

	*ack = '\0';
	RETURN_STR(aboo);
}

BUILT_IN_FUNCTION(function_ascii, word)
{
	char *aboo = NULL;

	if (!word || !*word)
		RETURN_EMPTY;

	aboo = m_strdup(ltoa((long) *word));
	while (*++word)
		m_3cat(&aboo, space, ltoa((long) *word));

	return aboo;
}

BUILT_IN_FUNCTION(function_which, word)
{
	char *file1;
	FILE *fp;
	int old_window_display;

	GET_STR_ARG(file1, word);
	file1 = m_strdup(file1);

	old_window_display = window_display;
	window_display = 0;

	if ((fp = uzfopen (&file1, (word && *word) ? word : get_string_var(LOAD_PATH_VAR))))
	{
		fclose (fp);
		window_display = old_window_display;
		return (file1);
	}
	else
	{
		new_free(&file1);
		window_display = old_window_display;
		RETURN_EMPTY;
	}
}


BUILT_IN_FUNCTION(function_isalpha, words)
{
	if (((*words >= 'a') && (*words <= 'z')) ||
	    ((*words >= 'A') && (*words <= 'Z')))
		RETURN_INT(1)
	else
		RETURN_INT(0)
}

BUILT_IN_FUNCTION(function_isdigit, words)
{
	if (((*words >= '0') && (*words <= '9')) ||
	    ((*words == '-') && ((*(words+1) >= '0') || (*(words+1) <= '9'))))
		RETURN_INT(1)
	else
		RETURN_INT(0)
}

BUILT_IN_FUNCTION(function_open, words)
{
	char *filename;
	GET_STR_ARG(filename, words);

	if (words && *words && toupper(*words) == 'R')
		RETURN_INT(open_file_for_read(filename))
	else if (words && *words && toupper(*words) == 'W')
		RETURN_INT(open_file_for_write(filename))
	else
		RETURN_EMPTY;
}

BUILT_IN_FUNCTION(function_close, words)
{
	RETURN_IF_EMPTY(words);
	RETURN_INT(file_close(atoi(new_next_arg(words, &words))))
}	

BUILT_IN_FUNCTION(function_write, words)
{
	char *fdc;
	GET_STR_ARG(fdc, words);
	RETURN_INT(file_write(atoi(fdc), words))
}

BUILT_IN_FUNCTION(function_writeb, words)
{
	char *fdc;
	GET_STR_ARG(fdc, words);
	RETURN_INT(file_writeb(atoi(fdc), words))
}

BUILT_IN_FUNCTION(function_read, words)
{
	char *fdc = NULL, *numb = NULL;

	GET_STR_ARG(fdc, words);
	if (words && *words)
		GET_STR_ARG(numb, words);

	if (numb)
		return file_readb (atoi(fdc), atoi(numb));
	else
		return file_read (atoi(fdc));
}

BUILT_IN_FUNCTION(function_eof, words)
{
	RETURN_IF_EMPTY(words);
	RETURN_INT(file_eof(atoi(new_next_arg(words, &words))))
}


BUILT_IN_FUNCTION(function_iptoname, words)
{
	RETURN_STR(ip_to_host(words));
}

BUILT_IN_FUNCTION(function_nametoip, words)
{
	RETURN_STR(host_to_ip(words));
}

BUILT_IN_FUNCTION(function_convert, words)
{
	RETURN_STR(one_to_another(words));
}

BUILT_IN_FUNCTION(function_translate, words)
{
	char *	oldc, 
	     *	newc, 
	     *	text,
	     *	ptr,
		delim;
	int 	size_old, 
		size_new,
		x;

	RETURN_IF_EMPTY(words);

	oldc = words;
	/* First character can be a slash.  If it is, we just skip over it */
	delim = *oldc++;
	newc = strchr(oldc, delim);

	if (!newc)
		RETURN_EMPTY;	/* no text in, no text out */

	text = strchr(++newc, delim);

	if (newc == oldc)
		RETURN_EMPTY;

	if (!text)
		RETURN_EMPTY;
	*text++ = '\0';

	if (newc == text)
	{
		*newc = '\0';
		newc = empty_string;
	}
	else
		newc[-1] = 0;

	/* this is cheating, but oh well, >;-) */
	text = m_strdup(text);

	size_new = strlen(newc);
	size_old = strlen(oldc);

	for (ptr = text; ptr && *ptr; ptr++)
	{
		for (x = 0;x < size_old;(void)x++)
		{
			if (*ptr == oldc[x])
			{
				/* Check to make sure we arent
				   just eliminating the character.
				   If we arent, put in the new char,
				   otherwise strcpy it away */
				if (size_new)
					*ptr = newc[(x<size_new)?x:size_new-1];
				else
				{
					strcpy (ptr, ptr+1);
					ptr--;
				}
				break;
			}
		}
	}
	return text;
}

BUILT_IN_FUNCTION(function_server_version, word)
{
	int servnum;
	int version;

	servnum = ((word && *word) ? atoi(next_arg(word, &word)) : primary_server);

	if (servnum > number_of_servers)
		RETURN_STR("unknown");

	version = get_server_version(servnum);

#ifdef COMPAT_27
	if (version == Server2_7)		RETURN_STR("2.7");
	else
#endif
	if (version == Server2_8) 		RETURN_STR("2.8");
	else if (version == Server_u2_8) 	RETURN_STR("u2.8");
	else if (version == Server_u2_9) 	RETURN_STR("u2.9");
	else if (version == Server_u2_10)	RETURN_STR("u2.10");
	else if (version == Server2_9) 		RETURN_STR("2.9");
	else if (version == Server_u3_0)	RETURN_STR("u3.0");

	RETURN_STR("Unknown");
}

BUILT_IN_FUNCTION(function_unlink, words)
{
	char *	expanded;
	int 	failure = 0;

	while (words && *words)
	{
		expanded = expand_twiddle(new_next_arg(words, &words));
		failure -= unlink(expanded);	
		new_free(&expanded);
	}

	RETURN_INT(failure)
}

BUILT_IN_FUNCTION(function_rename, words)
{
	char *	filename1, 
	     *	filename2;
	char *expanded1, *expanded2;
	int 	failure = 0;

	GET_STR_ARG(filename1, words)
	expanded1 = expand_twiddle(filename1);

	GET_STR_ARG(filename2, words)
	expanded2 = expand_twiddle(filename2);

	failure = rename(expanded1, expanded2);
	new_free(&expanded1);
	new_free(&expanded2);

	RETURN_INT(failure)
}

BUILT_IN_FUNCTION(function_rmdir, words)
{
	int 	failure = 0;
	char *expanded;

	while (words && *words)
	{
		expanded = expand_twiddle(new_next_arg(words, &words));
		failure -= rmdir(expanded);
		new_free(&expanded);
	}

	RETURN_INT(failure)
}

BUILT_IN_FUNCTION(function_truncate, words)
{
	int		num = 0;
	double		value = 0;
	char		buffer[BIG_BUFFER_SIZE],
			format[1024];

	GET_INT_ARG(num, words);
	GET_FLOAT_ARG(value, words);

	if (num < 0)
	{
		float foo;
		int end;

		sprintf(format, "%%.%de", -num-1);
		sprintf(buffer, format, value);
		foo = atof(buffer);
		sprintf(buffer, "%f", foo);
		end = strlen(buffer) - 1;
		if (end == 0)
			RETURN_EMPTY;
		while (buffer[end] == '0')
			end--;
		if (buffer[end] == '.')
			end--;
		buffer[end+1] = 0;
	}
	else if (num > 0)
	{
		sprintf(format, "%%10.%dlf", num);
		sprintf(buffer, format, value);
	}
	else
		RETURN_EMPTY;

	while (*buffer == ' ')
		strcpy(buffer, buffer+1);

	RETURN_STR(buffer);
}


/*
 * Apprantly, this was lifted from a CS client.  I reserve the right
 * to replace this code in future versions. (hop)
 */
/*
	I added this little function so that I can have stuff formatted
	into days, hours, minutes, seconds; but with d, h, m, s abreviations.
		-Taner
*/

BUILT_IN_FUNCTION(function_tdiff2, input)
{
	time_t	ltime;
	time_t	days,
		hours,
		minutes,
		seconds;
	char	tmp[80];
	char	*tstr;

	GET_INT_ARG(ltime, input);

	seconds = ltime % 60;
	ltime = (ltime - seconds) / 60;
	minutes = ltime%60;
	ltime = (ltime - minutes) / 60;
	hours = ltime % 24;
	days = (ltime - hours) / 24;
	tstr = tmp;

	if (days)
	{
		sprintf(tstr, "%ldd ", days);
		tstr += strlen(tstr);
	}
	if (hours)
	{
		sprintf(tstr, "%ldh ", hours);
		tstr += strlen(tstr);
	}
	if (minutes)
	{
		sprintf(tstr, "%ldm ", minutes);
		tstr += strlen(tstr);
	}
	if (seconds || (!days && !hours && !minutes))
	{
		sprintf(tstr, "%lds", seconds);
		tstr += strlen(tstr);
	}
	else
		*--tstr = 0;	/* chop off that space! */

	RETURN_STR(tmp);
}

/* 
 * The idea came from a CS client.  I wrote this from scratch, though.
 */
BUILT_IN_FUNCTION(function_utime, input)
{
	struct timeval tp;

	if (gettimeofday(&tp,NULL))
		RETURN_EMPTY;

	return m_sprintf("%ld %ld", tp.tv_sec, tp.tv_usec);
}


/*
 * This inverts any ansi sequence present in the string
 * from: Scott H Kilau <kilau@prairie.NoDak.edu>
 */
BUILT_IN_FUNCTION(function_stripansi, input)
{
	char	*cp;

	for (cp = input; *cp; cp++)
		/* Strip anything from 14 - 33, except ^O and ^V */
		if (*cp < 32 && *cp > 13)
			if (*cp != 15 && *cp != 22)
				*cp = (*cp & 127) | 64;

	RETURN_STR(input);
}

BUILT_IN_FUNCTION(function_servername, input)
{
	int sval;
	char *which;

	GET_INT_ARG(sval, input);

	/* garbage in, garbage out. */
	if (sval < 0 || sval >= number_of_servers)
		RETURN_EMPTY;

	/* First we try to see what the server thinks it name is */
	which = server_list[sval].itsname;

	/* Next we try what we think its name is */
	if (!which)
		which = server_list[sval].name;

	/* Ok. i give up, return a null. */
	RETURN_STR(which);
}

BUILT_IN_FUNCTION(function_lastserver, input)
{
	RETURN_INT(last_server)
}

BUILT_IN_FUNCTION(function_winserv, input)
{
	int win = 0;
	char *tmp;
	Window *winp;

	if (input && *input)
	{
		if ((tmp = new_next_arg(input, &input)))
			win = atoi(tmp);
	}
	if ((winp = get_window_by_refnum(win)))
		RETURN_INT(winp->server)

	RETURN_INT(-1)
}

BUILT_IN_FUNCTION(function_numwords, input)
{
	RETURN_INT(word_count(input))
}

BUILT_IN_FUNCTION(function_strlen, input)
{
	RETURN_INT(strlen(input))
}

BUILT_IN_FUNCTION(function_aliasctl, input)
{
	int list = -1;
	char *listc;
	enum { GET, SET, MATCH } op;

	GET_STR_ARG(listc, input);
	RETURN_IF_EMPTY(listc);
	if (!my_strnicmp(listc, "AS", 2))
		list = VAR_ALIAS;
	else if (!my_strnicmp(listc, "AL", 2))
		list = COMMAND_ALIAS;
	else
		RETURN_EMPTY;

	GET_STR_ARG(listc, input);
	RETURN_IF_EMPTY(listc);
	if (!my_strnicmp(listc, "G", 1))
		op = GET;
	else if (!my_strnicmp(listc, "S", 1))
		op = SET;
	else if (!my_strnicmp(listc, "M", 1))
		op = MATCH;
	else
		RETURN_EMPTY;

	GET_STR_ARG(listc, input);
	RETURN_IF_EMPTY(listc);

	switch (op)
	{
		case (GET) :
		{
			Alias *alias;
			if ((alias = find_alias(&(alias_list[list]), listc, 0, NULL, 1)))
				RETURN_STR(alias->stuff);
			else
				RETURN_EMPTY;
		}
		case (SET) :
		{
			add_alias(list, listc, input);
			RETURN_INT(1)
		}
		case (MATCH) :
		{
			char **mlist;
			char *mylist = NULL;
			int num, ctr;

			if (!my_stricmp(listc, "*"))
				listc = empty_string;

			upper(listc);
			mlist = match_alias(listc, &num, list);
			for (ctr = 0; ctr < num; ctr++)
			{
				if (mylist)
					malloc_strcat(&mylist, space);
				malloc_strcat(&mylist, mlist[ctr]);
				new_free((char **)&mlist[ctr]);
			}
			new_free((char **)&mlist);
			if (mylist)
				return mylist;
			RETURN_EMPTY;
		}
		default :
			yell("aliasctl: Error");
			RETURN_EMPTY;
	}
	RETURN_EMPTY;
}


/* 
 * Next two contributed by Scott H Kilau (sheik), who for some reason doesnt 
 * want to take credit for them. *shrug* >;-)
 *
 * Deciding not to be controversial, im keeping the original (contributed)
 * semantics of these two functions, which is to return 1 on success and
 * -1 on error.  If you dont like it, then tough. =)  I didnt write it, and
 * im not going to second guess any useful contributions.
 */
BUILT_IN_FUNCTION(function_fexist, words)
{
        char	FileBuf[BIG_BUFFER_SIZE+1];
	char	*filename, *fullname;

	if ((filename = new_next_arg(words, &words)))
	{
		if (*filename == '/')
			strcpy(FileBuf, filename);

		else if (*filename == '~') 
		{
			if (!(fullname = expand_twiddle(filename)))
				RETURN_INT(-1)

			strmcpy(FileBuf, fullname, BIG_BUFFER_SIZE);
			new_free(&fullname);
		}
		else 
		{
			getcwd(FileBuf, BIG_BUFFER_SIZE);
			strmcat(FileBuf, "/", BIG_BUFFER_SIZE);
			strmcat(FileBuf, filename, BIG_BUFFER_SIZE);
		}
		if (access(FileBuf, R_OK) == -1)
			RETURN_INT(-1)

		else
			RETURN_INT(1)
	}
	RETURN_INT(-1)
}

/* XXXX - ugh. do we really have to do a access() call first? */
BUILT_IN_FUNCTION(function_fsize, words)
{
        char	FileBuf[BIG_BUFFER_SIZE+1];
	char	*filename, *fullname;
        struct  stat    stat_buf;
	int	filesize = 0;

	filename = next_arg(words, &words);
	if (filename) 
	{
		if (*filename == '/')
			strcpy(FileBuf, filename);

		else if (*filename == '~') 
		{
			if (!(fullname = expand_twiddle(filename)))
				RETURN_INT(-1)

			strmcpy(FileBuf, fullname, BIG_BUFFER_SIZE);
			new_free(&fullname);
		}
		else 
		{
			getcwd(FileBuf, sizeof(FileBuf));
			strmcat(FileBuf, "/", BIG_BUFFER_SIZE);
			strmcat(FileBuf, filename, BIG_BUFFER_SIZE);
		}
		if (access(FileBuf, R_OK) == -1)
			RETURN_INT(-1)
		stat_file(FileBuf, &stat_buf);
		filesize = stat_buf.st_size;
		RETURN_INT(filesize)
	}
	RETURN_INT(-1)
}

/* 
 * Contributed by CrowMan
 * I changed two instances of "RETURN_INT(result)"
 * (where result was a null pointer) to RETURN_STR(empty_string)
 * because i dont think he meant to return a null pointer as an int value.
 */
/*
 * $crypt(password seed)
 * What it does: Returns a 13-char encrypted string when given a seed and
 *    password. Returns zero (0) if one or both args missing. Additional
 *    args ignored.
 * Caveats: Password truncated to 8 chars. Spaces allowed, but password
 *    must be inside "" quotes.
 * Credits: Thanks to Strongbow for showing me how crypt() works.
 * Written by: CrowMan
 */
BUILT_IN_FUNCTION(function_crypt, words)
{
        char pass[9];
        char seed[3];
        char *blah, *bleh, *crypt();

	GET_STR_ARG(blah, words)
	GET_STR_ARG(bleh, words)
	strmcpy(pass, blah, 8);
	strmcpy(seed, bleh, 2);
	RETURN_STR(crypt(pass, seed));
}

BUILT_IN_FUNCTION(function_info, words)
{
	char *which;
	extern char *compile_info;
	extern char *info_c_sum;

	GET_STR_ARG(which, words);

	     if (!my_strnicmp(which, "C", 1))
		RETURN_STR(compile_info);
	else if (!my_strnicmp(which, "O", 1))
		RETURN_STR(compile_time_options);
	else if (!my_strnicmp(which, "S", 1))
		RETURN_STR(info_c_sum);
	else
		RETURN_EMPTY;
	/* more to be added as neccesary */
}

/*
 * Based on a contribution made a very long time ago by wintrhawk
 */
BUILT_IN_FUNCTION(function_channelmode, word)
{
	char	channel[257];
	char    *booya = (char *) 0;
	int debug = 0;
	char *curr_chan;

	if (word && *word)
		strmcpy(channel, new_next_arg(word, &word), 254);
	else
		strmcpy(channel, empty_string, 254);

	curr_chan = get_channel_by_refnum(0);
	if (!curr_chan)
		curr_chan = empty_string;

	for (;;)
	{
		char    *mode;

		if (debug++ > 100)
			panic("function_channelmode: infinite loop");

		/* If we are at the end of the list */
		if (!*channel)
		{
			/* If there were other channels, stop */
			if (booya)
				break;

			/* Empty list -- use current channel */
			else
				strmcpy(channel, curr_chan, 256);
		}

		/* Not at end of list, look to see if channel is "*" */
		else if (!strcmp(channel, "*"))
			strmcpy(channel, curr_chan, 256);

		/* If there is still no channel, stop */
		if (!*channel)
			return (booya ? booya : m_strdup(empty_string));

		if ((mode = get_channel_mode(channel, curr_scr_win->server)))
			m_s3cat(&booya, space, (mode || *mode) ? mode : "*");

		if (!word || !*word)
			break;

		strmcpy(channel, new_next_arg(word, &word), 256);
	}

	if (!booya)
		RETURN_EMPTY;

	return (booya);
}

BUILT_IN_FUNCTION(function_geom, words)
{
	/* Erf. CO and LI are ints. (crowman) */
	return m_sprintf("%d %d", CO, LI);
}

BUILT_IN_FUNCTION(function_pass, words)
{
	char *lookfor;
	char *final, *ptr;

	GET_STR_ARG(lookfor, words);
	final = (char *)new_malloc(strlen(words) + 1);
	ptr = final;

	while (*words)
	{
		if (index(lookfor, *words))
			*ptr++ = *words;
		words++;
	}

	*ptr = 0;
	return final;
}

BUILT_IN_FUNCTION(function_repeat, words)
{
	int num;
	char *final = NULL;

	GET_INT_ARG(num, words);
	if (num < 1)
		RETURN_EMPTY;

	final = (char *)new_malloc(strlen(words) * num + 1);
	*final = 0;
	for (; num > 0; num--)
		strcat(final, words);
	return final;
}

BUILT_IN_FUNCTION(function_epic, words)
{
	RETURN_INT(1);
}

BUILT_IN_FUNCTION(function_winsize, words)
{
	int refnum;
	Window *win;

	if (words && *words)
	{
		GET_INT_ARG(refnum, words);
		win = get_window_by_refnum(refnum);
	}
	else
		win = curr_scr_win;

	if (!win)
		RETURN_EMPTY;

	RETURN_INT(win->display_size);
}


BUILT_IN_FUNCTION(function_umode, words)
{
	int servnum;

	if (words && *words)
	{
		GET_INT_ARG(servnum, words);
	}
	else
		servnum = from_server;

	RETURN_STR(get_umode(servnum));
}

#ifdef __STDC__
static int sort_it (const void *one, const void *two)
#else
static int sort_it (one, two)
const char *one, *two;
#endif
{
	return my_stricmp(*(char **)one, *(char **)two);
}

BUILT_IN_FUNCTION(function_sort, words)
{
	int wordc;
	char **wordl;

	wordc = splitw(words, &wordl);
	qsort((void *)wordl, wordc, sizeof(char *), sort_it);

	return unsplitw(wordl, wordc);	/* DONT USE RETURN_STR() HERE */
}

#ifdef __STDC__
static int num_sort_it (const void *one, const void *two)
#else
static int num_sort_it (one, two)
const char *one, *two;
#endif
{
	char *oneptr = *(char **)one;
	char *twoptr = *(char **)two;
	long val1, val2;

	while (*oneptr && *twoptr)
	{
		while (*oneptr && *twoptr && !(my_isdigit(oneptr)) && !(my_isdigit(twoptr)))
		{
			if (*oneptr != *twoptr)
				return (*oneptr - *twoptr);
			oneptr++, twoptr++;
		}

		if (!*oneptr || !*twoptr)
			break;

		val1 = strtol(oneptr, (char **)&oneptr, 10);
		val2 = strtol(twoptr, (char **)&twoptr, 10);
		if (val1 != val2)
			return val1 - val2;
	}
	return (*oneptr - *twoptr);
}

BUILT_IN_FUNCTION(function_numsort, words)
{
	int wordc;
	char **wordl;

	wordc = splitw(words, &wordl);
	qsort((void *)wordl, wordc, sizeof(char *), num_sort_it);

	return unsplitw(wordl, wordc);	/* DONT USE RETURN_STR() HERE */
}


BUILT_IN_FUNCTION(function_notify, words)
{
	int showon = -1, showserver = 0;
	char *firstw;

	while (words && *words)
	{
		firstw = new_next_arg(words, &words);
		if (!my_strnicmp(firstw, "on", 2))
		{
			showon = 1;
			continue;
		}
		if (!my_strnicmp(firstw, "off", 3))
		{
			showon = 0;
			continue;
		}
		if (!my_strnicmp(firstw, "serv", 4))
		{
			GET_INT_ARG(showserver, words);
		}
	}

	/* dont use RETURN_STR() here. */
	return get_notify_nicks(showserver, showon);
}

#ifdef NEED_GLOB
#define glob bsd_glob
#define globfree bsd_globfree
#endif

BUILT_IN_FUNCTION(function_glob, word)
{
#ifdef INCLUDE_GLOB_FUNCTION
	char *path, *path2, *retval = NULL;
	int numglobs, i;
	glob_t globbers;

	bzero(&globbers, sizeof(glob_t));
	while (word && *word)
	{
		GET_STR_ARG(path, word);
		path2 = expand_twiddle(path);
		if (!path2)
			path2 = path;

		numglobs = glob(path2, GLOB_MARK, NULL, &globbers);
		if (numglobs < 0)
			RETURN_INT(numglobs);
		for (i = 0; i < globbers.gl_pathc; i++)
			m_s3cat(&retval, space, globbers.gl_pathv[i]);

		globfree(&globbers);
	}

	RETURN_IF_EMPTY(retval);
	return retval;
#else
	RETURN_EMPTY;
#endif
}

BUILT_IN_FUNCTION(function_mkdir, words)
{
	int 	failure = 0;
	char *expanded;

	while (words && *words)
	{
		expanded = expand_twiddle(new_next_arg(words, &words));
		failure -= mkdir(expanded, 0777);
		new_free(&expanded);
	}

	RETURN_INT(failure)
}

BUILT_IN_FUNCTION(function_umask, words)
{
	int new_umask;
	GET_INT_ARG(new_umask, words);
	RETURN_INT(umask(new_umask));
}

BUILT_IN_FUNCTION(function_chmod, words)
{
	char *filearg, *after;
	int fd = -1;
	char *perm_s;
	mode_t perm;

	GET_STR_ARG(filearg, words);
	fd = (int) strtoul(filearg, &after, 10);
	GET_STR_ARG(perm_s, words);
	perm = (mode_t) strtol(perm_s, &perm_s, 8);

	if (after != words && *after == 0)
	{
		if (file_valid(fd))
			RETURN_INT(fchmod(fd, perm))
		else
			RETURN_EMPTY;
	}
	else
		RETURN_INT(chmod(filearg, perm))
}

BUILT_IN_FUNCTION(function_twiddle, words)
{
	if (words && *words)
		return expand_twiddle(new_next_arg(words, &words));

	RETURN_EMPTY;
}


/* 
 * Date: Sun, 29 Sep 1996 19:17:25 -0700
 * Author: Thomas Morgan <tmorgan@pobox.com>
 * Submitted-by: archon <nuance@twri.tamu.edu>
 *
 * $uniq (string of text)
 * Given a set of words, returns a new set of words with duplicates
 * removed.
 * EX: $uniq(one two one two) returns "one two"
 */
BUILT_IN_FUNCTION(function_uniq, word)
{
        char    **list = NULL, *booya = NULL;
        int     listc, listi;
	char	*tval;
	int	val1;

        listc = splitw(word, &list);

        for (listi = 0; listi < listc; listi++)
        {
                if (booya == NULL)
                        m_s3cat(&booya, space, list[listi]);
                else
                {
                        char *rmatch_input =
                                malloc(strlen(list[listi])
                                       + strlen(booya) + 2);

                        strcpy(rmatch_input, list[listi]);
                        strcat(rmatch_input, " ");
                        strcat(rmatch_input, booya);

			tval = function_rmatch(rmatch_input);
			val1 = atoi(tval);
			new_free(&tval);

			if (!val1)
                                m_s3cat(&booya, space, list[listi]);

                        free(rmatch_input);
                }
        }

        new_free((char **)&list);

        if (!booya)
                RETURN_EMPTY;

        return (booya);                         /* DONT USE RETURN_STR HERE! */
}

BUILT_IN_FUNCTION(function_winvisible, word)
{
	RETURN_INT(get_visible_by_refnum(word))
}

