
/*
 * COMMAND.C
 * (c) 1992-3 J.Harper
 *
 * Wack(ish) style command processor
 *
 * explanation time,
 * Each command string is built from clauses, the different types of
 * clause are,
 *
 * (symbol [arg clauses])   command or variable clause
 * `string'                 string clause (quotes nest!), strings can also
 * {string}		    be surrounded by curly brackets (to help ARexx
 *			    users :-)
 * number		    number (hex, octal or decimal) clause,
 * ~x			    ascii value of char x (number clause)
 * ^x			    control codes
 * @			    clause with no value (a placeholder?)
 *
 * escape sequences supported in strings (and characters) are
 *  \n		newline
 *  \r		carriage return
 *  \f		form feed
 *  \t		horizontal tab
 *  \\		backslash
 *  \`          ignored open quote
 *  \'          ignored close quote
 *  \0377	octal byte
 *  \0xff	hex byte
 *  \255	decimal byte
 *
 * There are two variable types, strings and numbers (32 bit signed ints),
 * actually command functions are treated as variables, this is not important.
 *
 * In a command clause there can be more clauses representing that command's
 * arguments, so an example command string could be,
 *  (settitle (format `%s %ld' `a string' 0x100))
 *
 * If a command string is to be passed as an argument it must be enclosed
 * in quotes to stop it being executed too soon, ie,
 *  (if 1 `(req `foo\nbar\n' `go on')')
 *
 * internal notes:
 * Command functions which return NULL are taken as signifying that the macro
 * currently being evaluated is to be aborted, the Value in the RES field
 * of that command is taken as the result of the whole macro (this is to
 * make a (return) command easy :-)
 */

#include "jed.h"
#include "jed_protos.h"

Prototype   GSYM *	addgsym		(STRPTR, BYTE, VALUE *);
Prototype   LSYM *	addlsym		(STRPTR, BYTE, VALUE *);
Prototype   BOOL	remsym		(STRPTR);
Local	    VALUE *	resolveclause	(STRPTR *, VALUE *);
Local	    BOOL	buildargv	(STRPTR *, LONG *, VALUE **);
Local	    VALUE *	resolvesym	(STRPTR, LONG, VALUE *, VALUE *);
Local	    VALUE *	execsym		(SYM *, LONG, VALUE *, VALUE *);
Local	    BOOL	expandargs	(STRPTR, LONG, VALUE *, LONG);
Prototype   VOID	releasevalue	(VALUE *);
Prototype   BOOL	dupvalue	(VALUE *, VALUE *);
Local	    VOID	releaseargs	(LONG, VALUE *);
Prototype   BOOL	templatecmd	(LONG, VALUE *, LONG);
Prototype   VALUE *	execstr		(STRPTR, VALUE *, BOOL, LONG, VALUE *);
Prototype   LSYM *	findlsym	(STRPTR);
Prototype   BOOL	findsargs	(LONG *, VALUE **);
Prototype   VOID	rxexecstring	(STRPTR, APTR);
Prototype   BOOL	scriptfile	(STRPTR);
Local	    STRPTR	nextclause	(STRPTR);
Prototype   VALUE *	cmd_symboldump	(LONG, VALUE *);
Local	    VOID	dumponesym	(SYM *, BPTR);

Prototype   const UBYTE NoArgMsg[];
Prototype   const UBYTE NotStringMsg[];
Prototype   const UBYTE NotNumerMsg[];
Prototype   const UBYTE BadArgMsg[];

const UBYTE NoArgMsg[] =     "syntax error: no argument specified";
const UBYTE NotStringMsg[] = "syntax error: not string value";
const UBYTE NotNumerMsg[] =  "syntax error: not numerical value";
const UBYTE BadArgMsg[] =    "syntax error: incorrect argument given";

Prototype   HASHNODE *SymbolTab[GSYMTABSIZE];
Prototype   CS	     CSList[];
Prototype   WORD     CSDepth;

HASHNODE *SymbolTab[GSYMTABSIZE];
CS	 CSList[MAX_CS_RECURSE];
WORD	 CSDepth;

/*
 * value (any strings) will be allocated or whatever.
 */
GSYM *
addgsym(STRPTR name, BYTE symType, VALUE *value)
{
    GSYM *newsym;
/*
 *  if(newsym = (GSYM *)findhash(SymbolTab, name))
 *  {
 *	releasevalue(&newsym->gs_Sym.sym_Value);
 *	newsym->gs_Sym.sym_Type = symType;
 *	dupvalue(&newsym->gs_Sym.sym_Value, value);
 *	return(newsym);
 *  }
 *  else
 */
    if(newsym = AllocVec(sizeof(GSYM), MEMF_CLEAR))
    {
	if(newsym->gs_Node.h_Name = savestring(name))
	{
	    inserthash(SymbolTab, &newsym->gs_Node, GSYMTABSIZE);
	    newsym->gs_Sym.sym_Type = symType;
	    dupvalue(&newsym->gs_Sym.sym_Value, value);
	    return(newsym);
	}
	FreeVec(newsym);
    }
    settitle(NoMemMsg);
    return(FALSE);
}

/*
 * value (any strings) will be allocated or whatever.
 */
LSYM *
addlsym(STRPTR name, BYTE symType, VALUE *value)
{
    LSYM *newsym;
/*
 *  if(newsym = findlsym(name))
 *  {
 *	releasevalue(&newsym->ls_Sym.sym_Value);
 *	newsym->ls_Sym.sym_Type = symType;
 *	dupvalue(&newsym->ls_Sym.sym_Value, value);
 *	return(newsym);
 *  }
 *  else
 */
    if(newsym = AllocVec(sizeof(LSYM), MEMF_CLEAR))
    {
	if(newsym->ls_Node.ln_Name = savestring(name))
	{
	    newsym->ls_Sym.sym_Type = symType;
	    dupvalue(&newsym->ls_Sym.sym_Value, value);
	    AddTail(&CSList[CSDepth].cs_Locals, &newsym->ls_Node);
	    return(newsym);
	}
	FreeVec(newsym);
    }
    settitle(NoMemMsg);
    return(FALSE);
}

/*
 * symbol's value is automatically discarded
 */
BOOL
remsym(STRPTR name)
{
    BOOL rc = FALSE;
    GSYM *gsym;
    LSYM *lsym;
    if(lsym = findlsym(name))
    {
	Remove(&lsym->ls_Node);
	freestring(lsym->ls_Node.ln_Name);
	releasevalue(&lsym->ls_Sym.sym_Value);
	FreeVec(lsym);
	rc = TRUE;
    }
    else if(gsym = findgsym(name))
    {
	removehash(SymbolTab, &gsym->gs_Node, GSYMTABSIZE);
	releasevalue(&gsym->gs_Sym.sym_Value);
	FreeVec(gsym);
	rc = TRUE;
    }
    else
	settitlefmt("error: unknown symbol %s", (LONG)name);
    return(rc);
}

/*
 * Evaluate the next clause in the string (deals with all other clauses
 * included in this clause).
 *
 * If this command returns FALSE it means a relatively major problem,
 * normally syntax errors but it could be that the last window was closed
 * in the middle of a command string, or that a clause was screwed up (ie,
 * unknown symbol or something), or we ran out of stack.
 */
Local VALUE *
resolveclause(STRPTR *clause, VALUE *result)
{
    STRPTR symbol = *clause;
    UBYTE c;
    if(stksize() <= MIN_STACK)
    {
	settitle("error: no stack available for deeper recursion");
	goto error;
    }
    symbol = nextclause(symbol);
    c = *symbol;
    if((c == '(') || isalpha(c) || (c == '_'))
    {
	UBYTE name[40], d;
	static const UBYTE dummy[] = ")";
	STRPTR tname = name, dummy_p = dummy;
	VALUE *argv;
	LONG argc;
	if(c == '(')
	    symbol = nextclause(symbol + 1);
	while((d = *symbol) && (!isspace(d)) && (d != ')'))
	{
	    *tname++ = d;
	    symbol++;
	}
	*tname = 0;
	if(c == '(' ? buildargv(&symbol, &argc, &argv) : buildargv(&dummy_p, &argc, &argv))
	{
	    result = resolvesym(name, argc, argv, result);
	    releaseargs(argc, argv);
	}
    }
    else if((c == '`') || (c == '{'))
    {
	STRPTR tempbuff = AllocVec(1024, 0L);
	LONG buffsize = 1024;

	if(tempbuff)
	{
	    WORD kg = 0;
	    WORD quotecount = 1;
	    LONG i = 0;

	    symbol++;
	    while(!kg)
	    {
		UBYTE c = *symbol++;
		switch(c)
		{
		    case 0:
			kg = -1;
			symbol--;
			settitle("syntax error: unbalanced quotes in string clause");
			break;
		    case '`':
		    case '{':
			quotecount++;
			break;
		    case '\'':
		    case '}':
			if(!(--quotecount))
			{
			    kg = 1;
			    c = 0;
			}
			break;
		    case '\\':
			if(quotecount == 1)
			    c = escchar(&symbol);
			else
			{
			    if(i >= buffsize)
			    {
				STRPTR newbuff = AllocVec(buffsize << 1, 0L);
				if(!newbuff)
				{
				    settitle(NoMemMsg);
				    FreeVec(tempbuff);
				    goto error;
				}
				memcpy(newbuff, tempbuff, buffsize);
				FreeVec(tempbuff);
				tempbuff = newbuff;
				buffsize <<= 1;
			    }
			    tempbuff[i++] = c;
			    c = *symbol++;
			}
			break;
		}
		if(i >= buffsize)
		{
		    STRPTR newbuff = AllocVec(buffsize << 1, 0L);
		    if(!newbuff)
		    {
			settitle(NoMemMsg);
			FreeVec(tempbuff);
			goto error;
		    }
		    memcpy(newbuff, tempbuff, buffsize);
		    FreeVec(tempbuff);
		    tempbuff = newbuff;
		    buffsize <<= 1;
		}
		tempbuff[i++] = c;
	    }
	    if(kg == 1)
	    {
		if(!(result->val_Value.String = savestring(tempbuff)))
		{
		    settitle(NoMemMsg);
		    FreeVec(tempbuff);
		    goto error;
		}
		result->val_Type = VTF_STRING;
	    }
	    else
	    {
		FreeVec(tempbuff);
		goto error;
	    }
	    FreeVec(tempbuff);
	}
	else
	{
	    settitle(NoMemMsg);
	    goto error;
	}
    }
    else if((isdigit(c)) || (c == '-'))
    {
	result->val_Value.Number = strtol(symbol, &symbol, 0);
	result->val_Type = VTF_NUMBER;
    }
    else if(c == '~')
    {
	if(symbol[1] == '\\')
	{
	    symbol += 2;
	    result->val_Value.Number = (LONG)escchar(&symbol);
	}
	else
	{
	    result->val_Value.Number = (LONG)symbol[1];
	    symbol += 2;
	}
	result->val_Type = VTF_NUMBER;
    }
    else if(c == '^')
    {
	result->val_Value.Number = (LONG)(toupper(symbol[1]) - '@');
	result->val_Type = VTF_NUMBER;
	symbol += 2;
    }
    else if(c == '@')
    {
	result->val_Type = VTF_NONE;
	symbol++;
    }
    else
    {
	settitle("syntax error: unrecognized clause type");
error:
	/* In a perfect world I'd just step over this clause, but since
	 * I'm lazy that can wait and for now I'll skip the whole string.
	 */
	symbol += strlen(symbol);
	result->val_Type = VTF_NONE;
	result = FALSE;
    }
    *clause = nextclause(symbol);
    return(result);
}

/*
 * build up the argv array
 *
 * Format of the argv array is,
 * argv[0]   - VALUE structure for result returned from command function,
 * argv[1-n] - VALUE structure of each argument.
 *
 * The argc passed is the number of arguments, doesn't include the
 * result.
 * The number of arguments is only limited by memory availability.
 */
Local BOOL
buildargv(STRPTR *args_p, LONG *argc_p, VALUE **argv_p)
{
    WORD maxargs = 9;
    VALUE *argv = AllocVec(sizeof(VALUE) * maxargs, MEMF_CLEAR);
    if(argv)
    {
	LONG i;
	STRPTR args = *args_p;
	for(i = 0; *args != ')'; i++)
	{
	    if(i == (maxargs - 1))
	    {
		VALUE *newargv = AllocVec(sizeof(VALUE) * (maxargs << 1), MEMF_CLEAR);
		if(!newargv)
		{
		    settitle(NoMemMsg);
		    releaseargs(i, argv);
		    return(FALSE);
		}
		memcpy(newargv, argv, sizeof(VALUE) * (i + 1));
		FreeVec(argv);
		argv = newargv;
		maxargs <<= 1;
	    }
	    if(!(resolveclause(&args, &argv[i + 1])))
	    {
		releaseargs(i, argv);
		return(FALSE);
	    }
	}
	*args_p = args + 1;
	*argv_p = argv;
	*argc_p = i;
	return(TRUE);
    }
    settitle(NoMemMsg);
    return(FALSE);
}

/*
 * find where a symbol is and resolve it
 */
Local VALUE *
resolvesym(STRPTR name, LONG argc, VALUE *argv, VALUE *result)
{
    LSYM *lsym;
    GSYM *gsym;
    UBYTE tmpbuf[256];

    /* Try local symbols first
     */
    if(lsym = findlsym(name))
	result = execsym(&lsym->ls_Sym, argc, argv, result);

    /* Look for a global symbol
     */
    else if(gsym = findgsym(name))
	result = execsym(&gsym->gs_Sym, argc, argv, result);

    /* See if we can find a DOS variable which fits
     */
    else if(GetVar(name, tmpbuf, 256, 0) > 0)
    {
	if(result->val_Value.String = savestring(tmpbuf))
	    result->val_Type = VTF_STRING;
	else
	{
	    settitle(NoMemMsg);
	    result->val_Type = VTF_NONE;
	    result = FALSE;
	}
    }

    /* Try for implicit REXX macro
     */
    else
    {
	if(strlen(name) < 200)
	{
	    BPTR lock;
	    sprintf(tmpbuf, "REXX:%s.jed", name);
	    if(lock = Lock(tmpbuf, SHARED_LOCK))
	    {
		UnLock(lock);
		strcpy(tmpbuf, name);
		if(expandargs(tmpbuf, argc, argv, 256))
		{
		    if(!(result->val_Value.Number = asyncRexxCmd(tmpbuf)))
			goto nosym;
		    result->val_Type = VTF_NUMBER;
		}
		else
		{
		    settitle("error: buffer overflow");
		    result = FALSE;
		}
	    }
	    else
		goto nosym;
	}
	else
	{
	    settitle(NoMemMsg);
	    result = FALSE;
	}
    }
    if(FALSE)
    {
nosym:
	settitlefmt("error: can't find symbol %s", (LONG)name);
	result = FALSE;
    }
    return(result);
}

/*
 * actually executes (or whatever) a symbol
 */
Local VALUE *
execsym(SYM *sym, LONG argc, VALUE *argv, VALUE *result)
{
    switch(sym->sym_Value.val_Type)
    {
	case VTF_FUNC:
	    if(!sym->sym_Value.val_Value.Func(argc, argv))
	    {
		*result = RES;
		result = NULL;
	    }
	    else
		*result = RES;
	    refresh();
	    break;
	case VTF_STRING:
	    if(sym->sym_Type == STF_COMMAND)
		result = execstr(sym->sym_Value.val_Value.String, result, TRUE, argc, argv);
	    else
		dupvalue(result, &sym->sym_Value);
	    break;
	default:
	    dupvalue(result, &sym->sym_Value);
	    break;
    }
    return(result);
}

/*
 * expands a commands argument clauses into a string (for REXX)
 */
Local BOOL
expandargs(STRPTR dest, LONG argc, VALUE *argv, LONG maxd)
{
    LONG i = strlen(dest);
    while(argc--)
    {
	switch(argv->val_Type)
	{
	    case VTF_STRING:
		LONG strl = strlen(argv->val_Value.String);
		if(i + strl < maxd)
		{
		    strcpy(dest + i, " ");
		    strcpy(dest + i + 1, argv->val_Value.String);
		    i += strl + 1;
		}
		else
		{
		    settitle("error: internal buffer overflow");
		    return(FALSE);
		}
		break;
	    case VTF_NUMBER:
		UBYTE nbuf[13];
		LONG strl;
		sprintf(nbuf, "%ld", argv->val_Value.Number);
		strl = strlen(nbuf);
		if(i + strl < maxd)
		{
		    strcpy(dest + i, " ");
		    strcpy(dest + i + 1, nbuf);
		    i += strl + 1;
		}
		else
		    return(FALSE);
		break;
	}
	argv++;
    }
    return(TRUE);
}

VOID
releasevalue(VALUE *value)
{
    if(value->val_Type == VTF_STRING)
	freestring(value->val_Value.String);
    value->val_Type = VTF_NONE;
}

BOOL
dupvalue(VALUE *dst, VALUE *src)
{
    BOOL rc = TRUE;
    if(src->val_Type == VTF_STRING)
    {
	if(dst->val_Value.String = savestring(src->val_Value.String))
	    dst->val_Type = VTF_STRING;
	else
	{
	    dst->val_Type = VTF_NONE;
	    rc = FALSE;
	}
    }
    else
	*dst = *src;
    return(rc);
}

/*
 * Doesn't touch the result field
 */
Local VOID
releaseargs(LONG argc, VALUE *argv)
{
    if(argv)
    {
	LONG i;
	for(i = 1; i <= argc; i++)
	{
	    if(argv[i].val_Type == VTF_STRING)
		freestring(argv[i].val_Value.String);
	}
	FreeVec(argv);
    }
}

/*
 * checks the arguments passed to a command against the provided
 * template. template has two bits per argument (ARG1 is LSB)
 * these bits represent,
 * 00	anything
 * 01	string
 * 10	number
 * macros are provided for this function, ie
 * if(TPLATE2(VTF_NUMBER, VTF_STRING))
 */
BOOL
templatecmd(LONG argc, VALUE *argv, LONG argTemplate)
{
    LONG i;
    for (i = 1; i <= argc; i++)
    {
	LONG thismask = argTemplate & 3;
	switch(thismask)
	{
	    case 1:
		if(ARG(i).val_Type != VTF_STRING)
		{
		    settitlefmt("syntax error: argument %ld should be a string", i);
		    return(FALSE);
		}
		break;
	    case 2:
		if(ARG(i).val_Type != VTF_NUMBER)
		{
		    settitlefmt("syntax error: argument %ld should be a number", i);
		    return(FALSE);
		}
		break;
	}
	argTemplate >>= 2;
    }
    if(argTemplate)
    {
	settitle("syntax error: too few arguments");
	return(FALSE);
    }
    return(TRUE);
}

/*
 * Executes a string of clauses, arguments can be passed and it can return
 * a result.
 *
 * A command in the macro which returns a null VALUE only aborts a macro
 * string (defined as those having arguments) - the result of the macro is
 * the result of the command that returned NULL.
 *
 * internal:
 * If this command returns 0 then it means that the result (in the VALUE
 * passed in) should be propagated back through the strings being executed
 * up to the macro (if available).
 * If a command returns 0 and result type is VTF_BREAK we just break from
 * this string returning a VTF_NONE value. Actually VTF_BREAK can handle
 * multiple depth breaking.
 */
VALUE *
execstr(STRPTR string, VALUE *result, BOOL args, LONG argc, VALUE *argv)
{
    if(++CSDepth < MAX_CS_RECURSE)
    {
	LSYM *thissym, *nextsym;
	CS *cs = &CSList[CSDepth];
	cs->cs_Args = args;
	cs->cs_Argc = argc;
	cs->cs_Argv = argv;
	NewList(&cs->cs_Locals);
	result->val_Type = VTF_NONE;
	while(*string)
	{
	    releasevalue(result);
	    if(!resolveclause(&string, result))
	    {
		if(result->val_Type == VTF_BREAK)
		{
		    if(!(--result->val_Value.Number))
			result->val_Type = VTF_NONE;
		    else
			result = FALSE;
		}
		else if(!args)
		    result = FALSE;
		break;
	    }
	}
	for(thissym = (LSYM *)cs->cs_Locals.lh_Head; thissym->ls_Node.ln_Succ; thissym = nextsym)
	{
	    nextsym = (LSYM *)thissym->ls_Node.ln_Succ;
	    freestring(thissym->ls_Node.ln_Name);
	    releasevalue(&thissym->ls_Sym.sym_Value);
	    FreeVec(thissym);
	}
    }
    else
    {
	settitle("error: command string recursion too deep");
	result->val_Type = VTF_NONE;
    }
    CSDepth--;
    return(result);
}

LSYM *
findlsym(STRPTR name)
{
    WORD i;
    for(i = CSDepth; i; i--)
    {
	LSYM *this;
	for(this = (LSYM *)CSList[i].cs_Locals.lh_Head; this->ls_Node.ln_Succ; this = (LSYM *)this->ls_Node.ln_Succ)
	{
	    if(!strcmp(name, this->ls_Node.ln_Name))
		return(this);
	}
    }
    return(FALSE);
}

/*
 * find the arguments given to the innermost macro
 */
BOOL
findsargs(LONG *argc_p, VALUE **argv_p)
{
    WORD i;
    for(i = CSDepth; i; i--)
    {
	if(CSList[i].cs_Args)
	{
	    *argc_p = CSList[i].cs_Argc;
	    *argv_p = CSList[i].cs_Argv;
	    return(TRUE);
	}
    }
    return(FALSE);
}

VOID
rxexecstring(STRPTR string, APTR rexxMsg)
{
    VALUE result;
    cursor(OFF);
    if(execstr(string, &result, FALSE, 0, NULL))
    {
	if(result.val_Type == VTF_STRING)
	    replyRexxCmd(rexxMsg, 0, 0, result.val_Value.String);
	else if(result.val_Type == VTF_NUMBER)
	    replyRexxCmd(rexxMsg, result.val_Value.Number, 0, NULL);
	else
	    replyRexxCmd(rexxMsg, 10, 0, NULL);
    }
    else
	replyRexxCmd(rexxMsg, 10, 0, NULL);
    releasevalue(&result);
    cursor(ON);
}

/*
 * Used by _main() to execute startup script
 */
BOOL
scriptfile(STRPTR fileName)
{
    STRPTR filetext;
    if(filetext = squirrelfile(fileName))
    {
	VALUE result;
	settitlefmt("executing %s...", (LONG)fileName);
	cursor(OFF);
	execstr(filetext, &result, FALSE, 0, NULL);
	releasevalue(&result);
	cursor(ON);
	freestring(filetext);
	return(TRUE);
    }
    return(FALSE);
}

/*
 * Steps over white space, if a character COMMENT_CHAR (';') is found
 * the rest of the line is skipped.
 *
 * IMPORTANT:
 *   If a command string is given to a command by enclosing it in quotes
 * it may contain comments (which won't be stripped until the command is
 * executed) but they (the comments) must NOT contain un-escaped quotes.
 */
Local STRPTR
nextclause(STRPTR str)
{
    STRPTR str2 = str;
    for(;;)
    {
	UBYTE c;
	while(isspace(c = *str2++))
	    ;

	if(c == COMMENT_CHAR)
	{
	    while((c = *str2++) && (c != '\n'))
		;
	    if(!c)
		str2--;
	}
	else
	    return(str2 - 1);
    }
}

/*
 * (symboldump `file' `type')
 *
 * type =
 *	    globals
 *	    locals
 *	    all
 */
VALUE *
cmd_symboldump(LONG argc, VALUE *argv)
{
    if(TPLATE2(VTF_STRING, VTF_STRING))
    {
	BPTR fh = Open(ARG1.val_Value.String, MODE_NEWFILE);
	if(fh)
	{
	    BOOL globs = FALSE;
	    BOOL locs = FALSE;
	    FPuts(fh, "symbol dump\n");
	    if(!stricmp("globals", ARG2.val_Value.String))
		globs = TRUE;
	    else if(!stricmp("locals", ARG2.val_Value.String))
		locs = TRUE;
	    else if(!stricmp("all", ARG2.val_Value.String))
	    {
		globs = TRUE;
		locs = TRUE;
	    }
	    if(globs)
	    {
		WORD i;
		FPuts(fh, "\nglobal symboltable\n");
		for(i = 0; i < GSYMTABSIZE; i++)
		{
		    GSYM *thissym = SymbolTab[i];
		    while(thissym)
		    {
			FPrintf(fh, "\t%s", (LONG)thissym->gs_Node.h_Name);
			dumponesym(&thissym->gs_Sym, fh);
			thissym = thissym->gs_Node.h_Next;
		    }
		}
	    }
	    if(locs)
	    {
		WORD i;
		FPuts(fh, "\nlocal symbols\n");
		for(i = 1; i <= CSDepth; i++)
		{
		    LSYM *thissym = (LSYM *)CSList[i].cs_Locals.lh_Head;
		    FPrintf(fh, "\tdepth %ld\n", i);
		    while(thissym->ls_Node.ln_Succ)
		    {
			FPrintf(fh, "\t\t%s", (LONG)thissym->ls_Node.ln_Name);
			dumponesym(&thissym->ls_Sym, fh);
			thissym = thissym->ls_Node.ln_Succ;
		    }
		}
	    }
	    Close(fh);
	    setnumres(TRUE);
	}
	else
	    setnumres(FALSE);
    }
    return(&RES);
}

Local VOID
dumponesym(SYM *sym, BPTR fh)
{
    if(sym->sym_Type == STF_COMMAND)
	FPuts(fh, " (command)");
    else if(sym->sym_Type == STF_VARIABLE)
	FPuts(fh, " (variable)");
    else
	FPuts(fh, " (unknown symbol type)");

    if(sym->sym_Value.val_Type == VTF_NONE)
	FPuts(fh, " = no value\n");
    else if(sym->sym_Value.val_Type == VTF_STRING)
	FPrintf(fh, " = {%s}\n", sym->sym_Value.val_Value.Number);
    else if(sym->sym_Value.val_Type == VTF_NUMBER)
	FPrintf(fh, " = 0x%lx\n", sym->sym_Value.val_Value.Number);
    else if(sym->sym_Value.val_Type == VTF_FUNC)
	FPuts(fh, " = primitive function\n");
    else
	FPuts(fh, " unknown value type\n");
}
