
/*
 * COMMAND.C
 * (c) 1992 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)
 * @			    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   SYMBOL *	addsymbol	(STRPTR, BYTE, BYTE, VOID *);
Prototype   BOOL	remsymbol	(STRPTR);
Local	    VALUE *	resolveclause	(STRPTR *, VALUE *);
Local	    VALUE *	execcmdsymbol	(SYMBOL *, STRPTR *, VALUE *);
Local	    BOOL	expandargs	(STRPTR, STRPTR *, LONG);
Prototype   VOID	releasevalue	(VALUE *);
Prototype   BOOL	dupvalue	(VALUE *, VALUE *);
Local	    VOID	releaseargs	(LONG, VALUE *);
Prototype   BOOL	templatecmd	(LONG, VALUE *, LONG);
Prototype   VALUE *	execmacro	(STRPTR, VALUE *, LONG, VALUE *);
Prototype   VALUE *	execstring	(STRPTR, VALUE *);
Prototype   VOID	rxexecstring	(STRPTR, APTR);
Prototype   BOOL	scriptfile	(STRPTR);
Local	    STRPTR	nextclause	(STRPTR);
Prototype   VALUE *	cmd_symboldump	(LONG, VALUE *);

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[HASHTABSIZE];
Prototype   MACCONTEXT *ExeList[];
Prototype   LONG	MacDepth;

HASHNODE   *SymbolTab[HASHTABSIZE];
MACCONTEXT *ExeList[MAX_MAC_RECURSE];
LONG	    MacDepth;

/*
 * value should have been allocated or whatever.
 */
SYMBOL *
addsymbol(STRPTR name, BYTE symType, BYTE valType, VOID *value)
{
    SYMBOL *newsym;
    if(newsym = (SYMBOL *)findhash(SymbolTab, name))
    {
	if(newsym->sym_ValType == VTF_STRING)
	    freestring(newsym->sym_Value.String);
	newsym->sym_SymType = symType;
	newsym->sym_ValType = valType;
	newsym->sym_Value.Generic = value;
	return(newsym);
    }
    else if(newsym = AllocVec(sizeof(SYMBOL), MEMF_CLEAR))
    {
	if(newsym->sym_Node.h_Name = savestring(name))
	{
	    inserthash(SymbolTab, &newsym->sym_Node);
	    newsym->sym_SymType = symType;
	    newsym->sym_ValType = valType;
	    newsym->sym_Value.Generic = value;
	    return(newsym);
	}
	FreeVec(newsym);
    }
    settitle(NoMemMsg);
    return(FALSE);
}

/*
 * symbol's value is automatically discarded
 */
BOOL
remsymbol(STRPTR name)
{
    SYMBOL *sym;
    if(sym = (SYMBOL *)findhash(SymbolTab, name))
    {
	removehash(SymbolTab, &sym->sym_Node);
	freestring(sym->sym_Node.h_Name);
	if(sym->sym_ValType == VTF_STRING)
	    freestring(sym->sym_Value.String);
	FreeVec(sym);
	return(TRUE);
    }
    settitlefmt("error: unknown symbol %s", (LONG)name);
    return(FALSE);
}

/*
 * 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;

    if(stksize() <= MIN_STACK)
    {
	settitle("error: no stack available for deeper recursion");
	goto error;
    }
    symbol = nextclause(symbol);
    if(*symbol == '(')
    {
	UBYTE c;
	SYMBOL *thissym;
	LOCALSYM *sym;

	/* This variable is static to reduce stack load for recursive calls to
	 * this routine - by the time it may be overwritten it doesn't matter,
	 * I hope.
	 */
	static UBYTE name[40];
	STRPTR tname = name;

	/* Temporary storage for DOS variables + REXX macro names
	 */
	static UBYTE tempbuf[256];

	symbol = nextclause(symbol + 1);
	while((c = *symbol++) && (!isspace(c)) && (c != ')'))
	    *tname++ = c;
	*tname = 0;
	if(c != ')')
	    symbol = nextclause(symbol - 1);
	else
	    symbol--;

	/* Try local symbols first
	 */
	if(MacDepth && (sym = (LOCALSYM *)FindName(&(ExeList[MacDepth]->mc_Locals), name)))
	{
	    dupvalue(result, &sym->l_Value);
	    while((c = *symbol++) && (c != ')'))
		;
	    if(!c)
		symbol--;
	}
	/* Look for a global symbol
	 */
	else if(thissym = findhash(SymbolTab, name))
	{
	    switch(thissym->sym_SymType)
	    {
		case STF_COMMAND:
		    result = execcmdsymbol(thissym, &symbol, result);
		    break;
		case STF_GLOBAL:
		    VALUE temp;
		    temp.val_Type = thissym->sym_ValType;
		    temp.val_Value.Generic = thissym->sym_Value.Generic;
		    dupvalue(result, &temp);
		    while((c = *symbol++) && (c != ')'))
			;
		    if(!c)
			symbol--;
		    break;
	    }
	}
	/* See if we can find a DOS variable which fits
	 */
	else if(GetVar(name, tempbuf, 256, 0) > 0)
	{
	    if(result->val_Value.String = savestring(tempbuf))
	    {
		result->val_Type = VTF_STRING;
		while((c = *symbol++) && (c != ')'))
		    ;
		if(!c)
		    symbol--;
	    }
	    else
	    {
		settitle(NoMemMsg);
		goto error;
	    }
	}
	/* Try for implicit REXX macro
	 */
	else
	{
	    if(strlen(name) < 200)
	    {
		BPTR lock;
		sprintf(tempbuf, "REXX:%s.jed", name);
		if(lock = Lock(tempbuf, SHARED_LOCK))
		{
		    UnLock(lock);
		    strcpy(tempbuf, name);
		    if(expandargs(tempbuf, &symbol, 256))
		    {
			if(!(result->val_Value.Number = asyncRexxCmd(tempbuf)))
			{
			    settitlefmt("error: can't find symbol %s", (LONG)name);
			    goto error;
			}
			result->val_Type = VTF_NUMBER;
		    }
		    else
		    {
			settitle("error: buffer overflow");
			goto error;
		    }
		}
		else
		{
		    settitlefmt("error: can't find symbol %s", (LONG)name);
		    goto error;
		}
	    }
	    else
	    {
		settitle(NoMemMsg);
		goto error;
	    }
	}
    }
    else if((*symbol == '`') || (*symbol == '{'))
    {
	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(*symbol)) || (*symbol == '-'))
    {
	result->val_Value.Number = strtol(symbol, &symbol, 0);
	result->val_Type = VTF_NUMBER;
    }
    else if(*symbol == '~')
    {
	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(*symbol == '@')
    {
	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);
}

/*
 * This function takes a symbol and the remainder of the clause which
 * it came from and returns the result of the symbol.
 * Commands(functions) or macros(strings) can be executed.
 *
 * All argument clauses are fully evaluated by calling resolveclause()
 * and their result put into the argv argument 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 VALUE *
execcmdsymbol(SYMBOL *cmd, STRPTR *args_p, VALUE *result)
{
    result->val_Type = VTF_NONE;

    /* If the last window is closed we really ought not execute any
     * commands since most commands don't check for a valid current
     * VW.
     */
    if(CurrVW)
    {
	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;

	    if(cmd->sym_ValType == VTF_FUNC)
	    {
		if(!(cmd->sym_Value.Func(i, argv)))
		{
		    *result = RES;
		    result = NULL;
		}
		else
		    *result = RES;

		/* Some jed specific code here :-(
		 */
		refresh();
	    }
	    else if(cmd->sym_ValType == VTF_STRING)
		result = execmacro(cmd->sym_Value.String, result, i, argv);
	    else
		result->val_Type = VTF_NONE;

	    releaseargs(i, argv);
	    return(result);
	}
	else
	    settitle(NoMemMsg);
    }
    return(FALSE);
}

/*
 * expands a commands argument clauses into a string (for REXX)
 */
Local BOOL
expandargs(STRPTR dest, STRPTR *clauses, LONG maxd)
{
    STRPTR args = *clauses;
    LONG i = strlen(dest);
    VALUE val;

    while(*args != ')')
    {
	if(resolveclause(&args, &val))
	{
	    switch(val.val_Type)
	    {
		case VTF_STRING:
		    LONG strl = strlen(val.val_Value.String);
		    if(i + strl < maxd)
		    {
			strcpy(dest + i, " ");
			strcpy(dest + i + 1, val.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", val.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;
	    }
	}
	else
	    return(FALSE);
    }
    *clauses = args + 1;
    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 to the macro
 * and it can return a result. (Because it gets a MACCONTEXT for itself).
 * See execstring() for more details about executing strings.
 *
 * A command in the macro which returns a null VALUE only aborts this macro
 * string - the result of the macro is the result of the command that
 * returned NULL.
 * Macros can also be broken as outlined in the execstring() func.
 */
VALUE *
execmacro(STRPTR string, VALUE *result, LONG argc, VALUE *argv)
{
    MACCONTEXT *mc;
    if(mc = AllocVec(sizeof(MACCONTEXT), MEMF_CLEAR))
    {
	LOCALSYM *thissym, *nextsym;

	if(++MacDepth < MAX_MAC_RECURSE)
	{
	    ExeList[MacDepth] = mc;
	    mc->mc_Argc = argc;
	    mc->mc_Argv = argv;
	    NewList(&mc->mc_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;
		    }
		    break;
		}
	    }
	    for(thissym = (LOCALSYM *)mc->mc_Locals.lh_Head; thissym->l_Node.ln_Succ; thissym = nextsym)
	    {
		nextsym = (LOCALSYM *)thissym->l_Node.ln_Succ;
		freestring(thissym->l_Node.ln_Name);
		releasevalue(&thissym->l_Value);
		FreeVec(thissym);
	    }
	    FreeVec(mc);
	    MacDepth--;
	}
	else
	{
	    settitle("error: macro recursion too deep");
	    result->val_Type = VTF_NONE;
	}
    }
    else
    {
	settitle(NoMemMsg);
	result->val_Type = VTF_NONE;
    }
    return(result);
}

/*
 * This executes a string as part of a macro. This is all a bit confusing
 * (to me anyway!) but the main difference is that if a clause in this
 * string (return)'s or has a syntax error the result will be that of the
 * whole macro. Also no arguments can be passed to this routine.
 *
 * 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 *
execstring(STRPTR string, VALUE *result)
{
    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
		result = FALSE;
	    break;
	}
    }
    return(result);
}

VOID
rxexecstring(STRPTR string, APTR rexxMsg)
{
    VALUE result;
    cursor(OFF);
    if(execstring(string, &result))
    {
	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);
	execstring(filetext, &result);
	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 < HASHTABSIZE; i++)
		{
		    SYMBOL *thissym = SymbolTab[i];
		    while(thissym)
		    {
			FPrintf(fh, "\t%s", (LONG)thissym->sym_Node.h_Name);
			if(thissym->sym_SymType == STF_COMMAND)
			    FPuts(fh, " (command)");
			else if(thissym->sym_SymType == STF_GLOBAL)
			    FPuts(fh, " (variable)");
			else
			    FPuts(fh, " (unknown symbol type)");

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

			thissym = thissym->sym_Node.h_Next;
		    }
		}
	    }
	    if(locs)
	    {
		WORD i;
		FPuts(fh, "\nlocal symbols\n");
		for(i = 1; i <= MacDepth; i++)
		{
		    LOCALSYM *thissym = (LOCALSYM *)ExeList[i]->mc_Locals.lh_Head;
		    FPrintf(fh, "\tmacro @ depth %ld\n", i);
		    while(thissym->l_Node.ln_Succ)
		    {
			FPrintf(fh, "\t\t%s", (LONG)thissym->l_Node.ln_Name);
			if(thissym->l_Value.val_Type == VTF_NONE)
			    FPuts(fh, " = no value\n");
			else if(thissym->l_Value.val_Type == VTF_STRING)
			    FPrintf(fh, " = <<%s>>\n", thissym->l_Value.val_Value.Number);
			else if(thissym->l_Value.val_Type == VTF_NUMBER)
			    FPrintf(fh, " = 0x%lx\n", thissym->l_Value.val_Value.Number);
			else
			    FPuts(fh, " unknown value type\n");

			thissym = thissym->l_Node.ln_Succ;
		    }
		}
	    }
	    Close(fh);
	    setnumres(TRUE);
	}
	else
	    setnumres(FALSE);
    }
    return(&RES);
}

