/* vi:set ts=8 sw=4 sts=4:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * eval.c: Expression evaluation.
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>	/* contains setenv() declaration for Amiga */
#endif
#ifdef AMIGA
# include <time.h>	/* for strftime() */
#endif

#ifdef WANT_EVAL

struct var
{
    char_u	*var_name;	/* name of variable */
    char	var_type;	/* VAR_NUMBER or VAR_STRING */
    union
    {
#if SIZEOF_INT <= 3		/* use long if int is smaller than 32 bits */
	long	var_number;	/* number value */
#else
	int	var_number;	/* number value */
#endif
	char_u	*var_string;	/* string value (Careful: can be NULL!) */
    }		var_val;
};

#define VAR_UNKNOWN 0
#define VAR_NUMBER  1
#define VAR_STRING  2

typedef struct var *	VAR;

/*
 * All user-defined internal variables are stored in variables.
 */
struct growarray    variables;
#define VAR_ENTRY(idx)	(((VAR)(variables.ga_data))[idx])

static VAR eval0 __ARGS((char_u *arg));
static VAR eval1 __ARGS((char_u **arg));
static VAR eval2 __ARGS((char_u **arg));
static VAR eval3 __ARGS((char_u **arg));
static VAR eval4 __ARGS((char_u **arg));
static VAR eval5 __ARGS((char_u **arg));
static VAR eval6 __ARGS((char_u **arg));
static VAR get_option_var __ARGS((char_u **arg, int error_msg));
static VAR get_string_var __ARGS((char_u **arg));
static VAR get_env_var __ARGS((char_u **arg));
static VAR get_func_var __ARGS((char_u *name, char_u **arg));
static void f_buffer_exists __ARGS((VAR *argvars, VAR retvar));
static void f_exists __ARGS((VAR *argvars, VAR retvar));
static void f_expand __ARGS((VAR *argvars, VAR retvar));
static void f_file_readable __ARGS((VAR *argvars, VAR retvar));
static void f_getline __ARGS((VAR *argvars, VAR retvar));
static void f_has __ARGS((VAR *argvars, VAR retvar));
static void f_highlight_exists __ARGS((VAR *argvars, VAR retvar));
static void f_strftime __ARGS((VAR *argvars, VAR retvar));
static void f_strlen __ARGS((VAR *argvars, VAR retvar));
static void f_substr __ARGS((VAR *argvars, VAR retvar));
static char_u *get_env_name __ARGS((char_u **arg));
static char_u *get_ident __ARGS((char_u **arg));
static int isnamechar __ARGS((int c));
static VAR get_var_var __ARGS((char_u *name, int error_msg));
static VAR alloc_var __ARGS((void));
static void free_var __ARGS((VAR varp));
static long get_var_number __ARGS((VAR varp));
static char_u *get_var_string __ARGS((VAR varp));
static char_u *get_var_string_buf __ARGS((VAR varp, char_u *buf));
static int find_var __ARGS((char_u *name));
static void list_one_var __ARGS((int i));
static void set_var __ARGS((char_u *name, VAR varp));

/*
 * Top level evaluation function, returning a boolean.
 * Returns TRUE or FALSE.
 * Sets "error" to TRUE if there was an error.
 */
    int
bool_eval(arg, error)
    char_u	*arg;
    int		*error;
{
    VAR		    varp;
    int		    retval;

    varp = eval0(arg);

    if (varp == NULL)
    {
	retval = FALSE;
	*error = TRUE;
    }
    else
    {
	retval = (get_var_number(varp) != 0);
	*error = FALSE;
    }

    free_var(varp);

    return retval;
}

/*
 * Top level evaluation function, returning a string.
 * Returns pointer to allocated memory, or NULL for failure.
 */
    char_u *
string_eval(arg)
    char_u	*arg;
{
    VAR		varp;
    char_u	*retval;

    varp = eval0(arg);

    if (varp == NULL)
	retval = NULL;
    else
	retval = vim_strsave(get_var_string(varp));

    free_var(varp);

    return retval;
}

/*
 * ":let var = expr"	assignment command.
 * ":let var"		list one variable value
 * ":let"		list all variable values
 */
    void
do_let(arg)
    char_u	*arg;
{
    char_u	    *expr;
    char_u	    *name;
    VAR		    varp;
    char_u	    *p;
    int		    c;
    int		    i;
#ifndef HAVE_SETENV
    char_u	    *envbuf;
#endif

    expr = vim_strchr(arg, '=');
    if (expr == NULL)
    {
	if (*arg == NUL)
	{
	    /*
	     * List all variables.
	     */
	    for (i = 0; i < variables.ga_len; ++i)
		if (VAR_ENTRY(i).var_name != NULL)
		    list_one_var(i);
	}
	else
	{
	    /*
	     * List one variable.
	     */
	    for (p = arg; isnamechar(*p); ++p)
		;
	    if (*p != NUL)
		EMSG(e_trailing);
	    else
	    {
		i = find_var(arg);
		if (i < 0)
		    EMSG2("Unknown variable: \"%s\"", arg);
		else
		    list_one_var(i);
	    }
	}
    }
    else
    {
	varp = eval0(expr + 1);
	if (varp != NULL)
	{
	    /*
	     * ":let $VAR = expr": Set environment variable.
	     */
	    if (*arg == '$')
	    {
		/*
		 * Find the end of the name;
		 */
		++arg;
		name = get_env_name(&arg);
		if (name != NULL)
		{
		    if (*skipwhite(arg) != '=')
			EMSG("Unexpected characters before '='");
		    else
		    {
			p = get_var_string(varp);
#ifdef HAVE_SETENV
# ifdef AMIGA
			setenv((char *)name, (char *)p);
# else
			setenv((char *)name, (char *)p, 1);
# endif
#else
			/*
			 * Putenv does not copy the string, it has to remain
			 * valid.  The allocated memory will never be freed.
			 */
			envbuf = alloc((unsigned)(STRLEN(p) +
							   STRLEN(name) + 2));
			if (envbuf != NULL)
			{
			    sprintf((char *)envbuf, "%s=%s", name, p);
			    putenv((char *)envbuf);
			}
#endif
		    }
		    vim_free(name);
		}
	    }

	    /*
	     * ":let 'option' = expr": Set option value.
	     */
	    else if (*arg == '\'')
	    {
		/*
		 * Find the end of the name;
		 */
		++arg;
		p = vim_strchr(arg, '\'');
		if (p == NULL)
		    EMSG2("Missing quote: %s", arg - 1);
		else if (*skipwhite(p + 1) != '=')
		    EMSG("Unexpected characters before '='");
		else
		{
		    *p = NUL;
		    set_option_value(arg, get_var_number(varp),
							get_var_string(varp));
		    *p = '\'';		    /* put ' back for error messages */
		}
	    }

	    /*
	     * ":let var = expr": Set internal variable.
	     */
	    else if (isnamechar(*arg) && !isdigit(*arg))
	    {
		/*
		 * Find the end of the name;
		 */
		for (p = arg; isnamechar(*p); ++p)
		    ;
		if (*skipwhite(p) != '=')
		    EMSG("Unexpected characters before '='");
		else
		{
		    c = *p;
		    *p = NUL;
		    set_var(arg, varp);
		    *p = c;		/* put char back for error messages */
		}
	    }

	    else
	    {
		EMSG2("Invalid argument: %s", arg);
	    }

	    free_var(varp);
	}
    }
}

/*
 * ":unlet var" command.
 */
    void
do_unlet(arg)
    char_u	*arg;
{
    char_u	    *name;
    char_u	    *name_end;
    int		    i;

    name_end = skiptowhite(arg);
    name = vim_strnsave(arg, (int)(name_end - arg));
    i = find_var(name);
    vim_free(name);

    if (i >= 0)		    /* existing variable, may need to free string */
    {
	vim_free(VAR_ENTRY(i).var_name);
	VAR_ENTRY(i).var_name = NULL;
	if (VAR_ENTRY(i).var_type == VAR_STRING)
	    vim_free(VAR_ENTRY(i).var_val.var_string);
	VAR_ENTRY(i).var_val.var_string = NULL;
    }
    else		    /* non-existing variable */
	EMSG2("No such variable: \"%s\"", arg);
}

/*
 * types for expressions.
 */
enum exp_type
{
    TYPE_UNKNOWN = 0,
    TYPE_EQUAL,		/* == */
    TYPE_NEQUAL,	/* != */
    TYPE_GREATER,	/* >  */
    TYPE_GEQUAL,	/* >= */
    TYPE_SMALLER,	/* <  */
    TYPE_SEQUAL,	/* <= */
    TYPE_MATCH,		/* =~ */
    TYPE_NOMATCH	/* !~ */
};

/*
 * Handle zero level expression.  This calls eval1() and handles error message
 * and nextcomm.
 */
    static VAR
eval0(arg)
    char_u  *arg;
{
    VAR		    varp;
    char_u	    *p;

    p = skipwhite(arg);
    varp = eval1(&p);
    if (varp == NULL || !ends_excmd(*p))
	EMSG2("Invalid expression: %s", arg);
    /* TODO: set nextcomm */

    return varp;
}

/*
 * Handle first level expression:
 *	expr2 || expr2 || expr2	    logical OR
 *
 * "arg" must point to the first non-white of the expression.
 * "arg" is advanced to the next non-white after the recognized expression.
 *
 * Returns a variable, or NULL when there is any error.
 */
    static VAR
eval1(arg)
    char_u	**arg;
{
    VAR	    var1, var2;
    long    n1, n2;

    /*
     * Get the first variable.
     */
    var1 = eval2(arg);
    if (var1 == NULL)
	return NULL;

    /*
     * Repeat until there is no following "||".
     */
    while ((*arg)[0] == '|' && (*arg)[1] == '|')
    {
	/*
	 * Get the second variable.
	 */
	*arg = skipwhite(*arg + 2);
	var2 = eval2(arg);
	if (var2 == NULL)
	{
	    free_var(var1);
	    return NULL;
	}

	/*
	 * Compute the result.
	 */
	n1 = get_var_number(var1);
	n2 = get_var_number(var2);
	free_var(var1);
	free_var(var2);
	var1 = alloc_var();
	if (var1 == NULL)
	    return NULL;
	var1->var_type = VAR_NUMBER;
	var1->var_val.var_number = (n1 || n2);
    }

    return var1;
}

/*
 * Handle second level expression:
 *	expr3 && expr3 && expr3	    logical AND
 *
 * "arg" must point to the first non-white of the expression.
 * "arg" is advanced to the next non-white after the recognized expression.
 *
 * Returns a variable, or NULL when there is any error.
 */
    static VAR
eval2(arg)
    char_u	**arg;
{
    VAR	    var1, var2;
    long    n1, n2;

    /*
     * Get the first variable.
     */
    var1 = eval3(arg);
    if (var1 == NULL)
	return NULL;

    /*
     * Repeat until there is no following "&&".
     */
    while ((*arg)[0] == '&' && (*arg)[1] == '&')
    {
	/*
	 * Get the second variable.
	 */
	*arg = skipwhite(*arg + 2);
	var2 = eval3(arg);
	if (var2 == NULL)
	{
	    free_var(var1);
	    return NULL;
	}

	/*
	 * Compute the result.
	 */
	n1 = get_var_number(var1);
	n2 = get_var_number(var2);
	free_var(var1);
	free_var(var2);
	var1 = alloc_var();
	if (var1 == NULL)
	    return NULL;
	var1->var_type = VAR_NUMBER;
	var1->var_val.var_number = (n1 && n2);
    }

    return var1;
}

/*
 * Handle third level expression:
 *	var1 == var2
 *	var1 != var2
 *	var1 > var2
 *	var1 >= var2
 *	var1 < var2
 *	var1 <= var2
 *
 * "arg" must point to the first non-white of the expression.
 * "arg" is advanced to the next non-white after the recognized expression.
 *
 * Returns a variable, or NULL when there is any error.
 */
    static VAR
eval3(arg)
    char_u	**arg;
{
    VAR			var1, var2;
    char_u		*p;
    int			i;
    enum exp_type	type = TYPE_UNKNOWN;
    int			len = 2;
    long		n1 = FALSE, n2;
    char_u		*s1, *s2;
    char_u		buf1[NUMBUFLEN], buf2[NUMBUFLEN];
    vim_regexp		*prog;

    /*
     * Get the first variable.
     */
    var1 = eval4(arg);
    if (var1 == NULL)
	return NULL;

    p = *arg;
    switch (p[0])
    {
	case '=':   if (p[1] == '=')
			type = TYPE_EQUAL;
		    else if (p[1] == '~')
			type = TYPE_MATCH;
		    break;
	case '!':   if (p[1] == '=')
			type = TYPE_NEQUAL;
		    else if (p[1] == '~')
			type = TYPE_NOMATCH;
		    break;
	case '>':   if (p[1] != '=')
		    {
			type = TYPE_GREATER;
			len = 1;
		    }
		    else
			type = TYPE_GEQUAL;
		    break;
	case '<':   if (p[1] != '=')
		    {
			type = TYPE_SMALLER;
			len = 1;
		    }
		    else
			type = TYPE_SEQUAL;
		    break;
    }

    /*
     * If there is a comparitive operator, use it.
     */
    if (type != TYPE_UNKNOWN)
    {
	/*
	 * Get the second variable.
	 */
	*arg = skipwhite(p + len);
	var2 = eval4(arg);
	if (var2 == NULL)
	{
	    free_var(var1);
	    return NULL;
	}

	/*
	 * If one of the two variables is a number, compare as a number.
	 * When using "=~" or "!~", compare as string.
	 */
	if ((var1->var_type == VAR_NUMBER || var2->var_type == VAR_NUMBER) &&
		type != TYPE_MATCH && type != TYPE_NOMATCH)
	{
	    n1 = get_var_number(var1);
	    n2 = get_var_number(var2);
	    switch (type)
	    {
		case TYPE_EQUAL:    n1 = (n1 == n2); break;
		case TYPE_NEQUAL:   n1 = (n1 != n2); break;
		case TYPE_GREATER:  n1 = (n1 > n2); break;
		case TYPE_GEQUAL:   n1 = (n1 >= n2); break;
		case TYPE_SMALLER:  n1 = (n1 < n2); break;
		case TYPE_SEQUAL:   n1 = (n1 <= n2); break;
		case TYPE_UNKNOWN:
		case TYPE_MATCH:
		case TYPE_NOMATCH:  break;  /* avoid gcc warning */
	    }
	}
	else
	{
	    s1 = get_var_string_buf(var1, buf1);
	    s2 = get_var_string_buf(var2, buf2);
	    i = STRCMP(s1, s2);
	    switch (type)
	    {
		case TYPE_EQUAL:    n1 = (i == 0); break;
		case TYPE_NEQUAL:   n1 = (i != 0); break;
		case TYPE_GREATER:  n1 = (i > 0); break;
		case TYPE_GEQUAL:   n1 = (i >= 0); break;
		case TYPE_SMALLER:  n1 = (i < 0); break;
		case TYPE_SEQUAL:   n1 = (i <= 0); break;
		case TYPE_MATCH:
		case TYPE_NOMATCH:  reg_ic = p_ic;
				    prog = vim_regcomp(s2, TRUE);
				    if (prog != NULL)
				    {
					n1 = vim_regexec(prog, s1, TRUE);
					vim_free(prog);
				    }
				    break;
		case TYPE_UNKNOWN:  break;  /* avoid gcc warning */
	    }
	}
	free_var(var1);
	free_var(var2);
	var1 = alloc_var();
	if (var1 == NULL)
	    return NULL;
	var1->var_type = VAR_NUMBER;
	var1->var_val.var_number = n1;
    }

    return var1;
}

/*
 * Handle fourth level expression:
 *	+	number addition
 *	-	number subtraction
 *	.	string concatenation
 *
 * "arg" must point to the first non-white of the expression.
 * "arg" is advanced to the next non-white after the recognized expression.
 *
 * Returns a variable, or NULL when there is any error.
 */
    static VAR
eval4(arg)
    char_u	**arg;
{
    VAR	    var1, var2;
    VAR	    varp;
    int	    c;
    long    n1 = 0, n2 = 0;
    char_u  *s1 = NULL, *s2 = NULL;
    char_u  buf1[NUMBUFLEN], buf2[NUMBUFLEN];
    char_u  *p;

    /*
     * Get the first variable.
     */
    var1 = eval5(arg);
    if (var1 == NULL)
	return NULL;

    /*
     * Repeat computing, until no '+', '-' or '.' is following.
     */
    for (;;)
    {
	c = **arg;
	if (c != '+' && c != '-' && c != '.')
	    break;

	/*
	 * Get the second variable.
	 */
	*arg = skipwhite(*arg + 1);
	var2 = eval5(arg);
	if (var2 == NULL)
	{
	    free_var(var1);
	    return NULL;
	}

	/*
	 * Compute the result.
	 */
	varp = alloc_var();
	if (varp == NULL)
	{
	    free_var(var1);
	    free_var(var2);
	    return NULL;
	}
	if (c == '.')
	{
	    s1 = get_var_string_buf(var1, buf1);
	    s2 = get_var_string_buf(var2, buf2);
	    varp->var_type = VAR_STRING;
	    p = alloc((unsigned)(STRLEN(s1) + STRLEN(s2) + 1));
	    if (p != NULL)
	    {
		STRCPY(p, s1);
		STRCAT(p, s2);
		varp->var_val.var_string = p;
	    }
	}
	else
	{
	    n1 = get_var_number(var1);
	    n2 = get_var_number(var2);
	    varp->var_type = VAR_NUMBER;
	    if (c == '+')
		varp->var_val.var_number = (n1 + n2);
	    else
		varp->var_val.var_number = (n1 - n2);
	}
	free_var(var1);
	free_var(var2);
	var1 = varp;
    }
    return var1;
}

/*
 * Handle fifth level expression:
 *	*	number multiplication
 *	/	number division
 *	%	number modulo
 *
 * "arg" must point to the first non-white of the expression.
 * "arg" is advanced to the next non-white after the recognized expression.
 *
 * Returns a variable, or NULL when there is any error.
 */
    static VAR
eval5(arg)
    char_u	**arg;
{
    VAR	    var1, var2;
    int	    c;
    long    n1, n2;

    /*
     * Get the first variable.
     */
    var1 = eval6(arg);
    if (var1 == NULL)
	return NULL;

    /*
     * Repeat computing, until no '*', '/' or '%' is following.
     */
    for (;;)
    {
	c = **arg;
	if (c != '*' && c != '/' && c != '%')
	    break;

	/*
	 * Get the second variable.
	 */
	*arg = skipwhite(*arg + 1);
	var2 = eval6(arg);
	if (var2 == NULL)
	{
	    free_var(var1);
	    return NULL;
	}

	/*
	 * Compute the result.
	 */
	n1 = get_var_number(var1);
	n2 = get_var_number(var2);
	free_var(var1);
	free_var(var2);
	var1 = alloc_var();
	if (var1 == NULL)
	    return NULL;
	var1->var_type = VAR_NUMBER;
	if (c == '*')
	    var1->var_val.var_number = (n1 * n2);
	else if (c == '/')
	{
	    if (n2 == 0)	/* give an error message? */
		var1->var_val.var_number = 0x7fffffff;
	    else
		var1->var_val.var_number = (n1 / n2);
	}
	else
	{
	    if (n2 == 0)	/* give an error message? */
		var1->var_val.var_number = 0;
	    else
		var1->var_val.var_number = (n1 % n2);
	}
    }

    return var1;
}

/*
 * Handle sixth level expression:
 *  number	    number constant
 *  "string"	    string contstant
 *  'option-name'   option value
 *  identifier	    variable value
 *  $VAR	    environment variable
 *  (expression)    nested expression
 *
 *  Also handle:
 *  ! in front	    logical NOT
 *  trailing []	    subscript in String
 *
 * "arg" must point to the first non-white of the expression.
 * "arg" is advanced to the next non-white after the recognized expression.
 *
 * Returns a variable, or NULL when there is any error.
 */
    static VAR
eval6(arg)
    char_u	**arg;
{
    VAR		retvar = NULL;
    VAR		varp;
    long	n;
    int		len;
    char_u	*s;
    int		not = FALSE;
    int		val;
    char_u	*errormsg;

    /*
     * Check for '!' character.
     */
    if (**arg == '!')
    {
	not = TRUE;
	*arg = skipwhite(*arg + 1);
    }

    switch (**arg)
    {
    /*
     * Number constant.
     */
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':	n = vim_str2nr(*arg, NULL, &len, TRUE, TRUE);
		*arg += len;
		retvar = alloc_var();
		if (retvar != NULL)
		{
		    retvar->var_type = VAR_NUMBER;
		    retvar->var_val.var_number = n;
		}
		break;

    /*
     * String constant: "string".
     */
    case '\"':	retvar = get_string_var(arg);
		break;

    /*
     * Option value: 'name'
     */
    case '\'':	retvar = get_option_var(arg, TRUE);
		break;

    /*
     * nested expression: (expression).
     */
    case '(':	*arg = skipwhite(*arg + 1);
		retvar = eval1(arg);
		if (**arg != ')')
		    EMSG("Missing ')'");
		else
		    ++*arg;
		break;

    /*
     * Environment variable: $VAR.
     */
    case '$':	retvar = get_env_var(arg);
		break;

    /*
     * current or alternate file name, et. al.
     */
    case '%':	
    case '#':
    case '<':	/*
		 * Be tolerant: when there is an error, return an empty string.
		 */
		s = eval_vars(*arg, &len, NULL, &errormsg);
		retvar = alloc_var();
		if (retvar != NULL)
		{
		    retvar->var_type = VAR_STRING;
		    retvar->var_val.var_string = s;
		}
		*arg += len;
		break;

    /*
     * Must be a variable or function name then.
     */
    default:	s = get_ident(arg);
		if (s != NULL)
		{
		    if (**arg == '(')
			retvar = get_func_var(s, arg);
		    else
			retvar = get_var_var(s, TRUE);
		}
		break;
    }
    *arg = skipwhite(*arg);

    /*
     * Handle expr[expr] subscript.
     */
    if (**arg == '[' && retvar != NULL)
    {
	/*
	 * Get the variable from inside the [].
	 */
	*arg = skipwhite(*arg + 1);
	varp = eval1(arg);
	if (varp == NULL)
	{
	    free_var(retvar);
	    return NULL;
	}
	s = get_var_string(retvar);
	n = get_var_number(varp);
	free_var(varp);

	/*
	 * Check for the ']' and skip it.
	 */
	if (**arg != ']')
	{
	    EMSG("Missing ']'");
	    free_var(retvar);
	    return NULL;
	}
	*arg = skipwhite(*arg + 1);

	/*
	 * The resulting variable is a string of a single character.
	 * If the index is too big, the result is empty.
	 */
	varp = alloc_var();
	if (varp == NULL)
	{
	    free_var(retvar);
	    return NULL;
	}
	varp->var_type = VAR_STRING;
	if (n >= (long)STRLEN(s))
	    varp->var_val.var_string = NULL;
	else
	    varp->var_val.var_string = vim_strnsave(s + n, 1);
	free_var(retvar);
	retvar = varp;
    }

    /*
     * Apply logical NOT.
     */
    if (not && retvar != NULL)
    {
	val = !get_var_number(retvar);
	free_var(retvar);
	retvar = alloc_var();
	if (retvar != NULL)
	{
	    retvar->var_type = VAR_NUMBER;
	    retvar->var_val.var_number = val;
	}
    }

    return retvar;
}

/*
 * Allocate a variable for an option value.
 */
    static VAR
get_option_var(arg, error_msg)
    char_u	**arg;
    int		error_msg;	    /* give error message for invalid name */
{
    VAR		retvar;
    char_u	*option_end;
    long	numval;
    char_u	*stringval;
    int		opt_type;

    /*
     * Isolate the option name between single quotes and find its value.
     */
    option_end = vim_strchr(*arg + 1, '\'');
    if (option_end == NULL)
    {
	EMSG2("Missing quote: %s", *arg);
	return NULL;
    }

    *option_end = NUL;
    opt_type = get_option_value(*arg + 1, &numval, &stringval);
    if (opt_type < 0)		    /* invalid name */
    {
	if (error_msg)
	    EMSG2("Unknown option: %s", *arg + 1);
	retvar = NULL;
    }
    else
    {
	/*
	 * Store the value in a variable.
	 */
	retvar = alloc_var();
	if (retvar == NULL)
	{
	    if (opt_type == 0)
		vim_free(stringval);
	}
	else
	{
	    if (opt_type == 1)	    /* number option */
	    {
		retvar->var_type = VAR_NUMBER;
		retvar->var_val.var_number = numval;
	    }
	    else		    /* string option */
	    {
		retvar->var_type = VAR_STRING;
		retvar->var_val.var_string = stringval;
	    }
	}
    }

    *option_end = '\'';		    /* put ' back for error messages */
    *arg = option_end + 1;

    return retvar;
}

/*
 * Allocate a variable for an string constant.
 */
    static VAR
get_string_var(arg)
    char_u	**arg;
{
    char_u	*p;
    char_u	*name;
    VAR		retvar;
    int		i;
    int		n;

    /*
     * Find the end of the string, skipping backslashed characters.
     */
    for (p = *arg + 1; *p && *p != '\"'; ++p)
	if (*p == '\\' && p[1] != NUL)
	    ++p;
    if (*p != '\"')
    {
	EMSG2("Missing quote: %s", *arg);
	return NULL;
    }

    /*
     * Copy the string into allocated memory, handling backslashed
     * characters.
     */
    name = alloc((unsigned)(p - *arg));
    if (name == NULL)
	return NULL;

    i = 0;
    for (p = *arg + 1; *p && *p != '\"'; ++p)
    {
	if (*p == '\\')
	{
	    switch (*++p)
	    {
		case 'b': name[i++] = BS; break;
		case 'e': name[i++] = ESC; break;
		case 'f': name[i++] = FF; break;
		case 'n': name[i++] = NL; break;
		case 'r': name[i++] = CR; break;
		case 't': name[i++] = TAB; break;

		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9': name[i++] = (char_u)vim_str2nr(p, NULL, &n, TRUE, TRUE);
			  p += n - 1;
			  break;

		default:  name[i++] = *p; break;
	    }
	}
	else
	    name[i++] = *p;
    }
    name[i] = NUL;
    *arg = p + 1;

    retvar = alloc_var();
    if (retvar == NULL)
	vim_free(name);
    else
    {
	retvar->var_type = VAR_STRING;
	retvar->var_val.var_string = name;
    }

    return retvar;
}

/*
 * Allocate a variable for an environment variable.
 */
    static VAR
get_env_var(arg)
    char_u	**arg;
{
    char_u	*name;
    VAR		retvar;
    char_u	*string;

    /*
     * Find the end of the name;
     */
    ++*arg;
    name = get_env_name(arg);
    if (name == NULL)
	return NULL;

    string = vim_getenv(name);
    vim_free(name);

    /*
     * Allocate a variable.  If the environment variable was not set, silently
     * assume it is empty.
     */
    retvar = alloc_var();
    if (retvar == NULL)
	return NULL;
    retvar->var_type = VAR_STRING;
    if (string != NULL)
	retvar->var_val.var_string = vim_strsave(string);

    return retvar;
}

/*
 * Allocate a variable for the result of a function.
 */
    static VAR
get_func_var(name, arg)
    char_u	*name;		/* name of the function */
    char_u	**arg;		/* argument, pointing to the '(' */
{
    char_u	*argp;
    VAR		retvar = NULL;
#define MAX_FUNC_ARGS	3
    VAR		argvars[MAX_FUNC_ARGS];	/* vars for arguments */
    int		argcount = 0;		/* number of arguments found */
#define ERROR_NONE	0
#define ERROR_INVARG	1
#define ERROR_UNKOWN	2
#define ERROR_OTHER	3
    int		error = ERROR_NONE;
    int		i;
    static struct fst
    {
	char	*f_name;	/* function name */
	char	f_argcount;	/* number of arguments */
	void	(*f_func) __ARGS((VAR *args, VAR rvar));    /* impl. function */
    } functions[] =
    {{"buffer_exists",	    1, f_buffer_exists},
     {"exists",		    1, f_exists},
     {"expand",		    1, f_expand},
     {"file_readable",	    1, f_file_readable},
     {"getline",	    1, f_getline},
     {"has",		    1, f_has},
     {"highlight_exists",   1, f_highlight_exists},
#ifdef HAVE_STRFTIME
     {"strftime",	    1, f_strftime},
#endif
     {"strlen",		    1, f_strlen},
     {"substr",		    3, f_substr},
    };

    /*
     * Get the arguments.
     */
    argp = *arg;
    while (*argp != ')' && argcount < MAX_FUNC_ARGS)
    {
	argp = skipwhite(argp + 1);	    /* skip the '(' or ',' */
	if ((argvars[argcount] = eval1(&argp)) == NULL)
	{
	    error = ERROR_OTHER;
	    break;
	}
	++argcount;
	if (*argp != ',')
	    break;
    }
    if (*argp != ')')
	error = ERROR_INVARG;

    if (!error)
    {
	/*
	 * Allocate a variable for the result.
	 */
	retvar = alloc_var();
	if (retvar != NULL)
	{
	    retvar->var_type = VAR_NUMBER;	/* default is number retvar */

	    /*
	     * Find the function name in the table, call its implementation.
	     */
	    error = ERROR_UNKOWN;
	    for (i = 0; i < (int)(sizeof(functions) / sizeof(struct fst)); ++i)
		if (STRCMP(name, functions[i].f_name) == 0)
		{
		    if (argcount != functions[i].f_argcount)
			error = ERROR_INVARG;
		    else
		    {
			functions[i].f_func(argvars, retvar);
			error = ERROR_NONE;
		    }
		    break;
		}

	    if (error == ERROR_UNKOWN)
		EMSG2("Unknown function: %s", name);
	}

	*arg = skipwhite(argp + 1);
	if (error != ERROR_NONE)
	{
	    free_var(retvar);
	    retvar = NULL;
	}
    }

    while (--argcount >= 0)
	free_var(argvars[argcount]);

    if (error == ERROR_INVARG)
	EMSG2("Invalid arguments for function %s", name);

    vim_free(name);
    return retvar;
}

/*
 * "buffer_exists()" function.
 */
    static void
f_buffer_exists(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    int		n;

    if (argvars[0]->var_type == VAR_NUMBER)
	n = (buflist_findnr((int)argvars[0]->var_val.var_number) != NULL);
    else if (argvars[0]->var_val.var_string != NULL)
	n = (buflist_findname(argvars[0]->var_val.var_string) != NULL);
    else
	n = FALSE;

    retvar->var_val.var_number = n;
}

/*
 * "exists()" function
 */
    static void
f_exists(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    char_u	*p, *s;
    VAR		varp;
    int		n = FALSE;

    p = get_var_string(argvars[0]);
    if (*p == '$')		    /* environment variable */
    {
	++p;
	p = get_env_name(&p);
	if (p != NULL)
	{
	    n = (vim_getenv(p) != NULL);
	    vim_free(p);
	}
    }
    else
    {
	if (*p == '\'')		    /* option */
	    varp = get_option_var(&p, FALSE);
	else			    /* internal variable */
	{
	    s = get_ident(&p);
	    if (s == NULL)
		varp = NULL;
	    else
		varp = get_var_var(s, FALSE);
	}
	if (varp != NULL)
	{
	    free_var(varp);
	    n = TRUE;
	}
    }

    retvar->var_val.var_number = n;
}

/*
 * "expand()" function
 */
    static void
f_expand(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    retvar->var_type = VAR_STRING;
    retvar->var_val.var_string = ExpandOne(get_var_string(argvars[0]),
					  NULL, WILD_LIST_NOTFOUND, WILD_ALL);
}

/*
 * "file_readable()" function
 */
    static void
f_file_readable(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    FILE	*fd;
    char_u	*p;
    int		n;

    p = get_var_string(argvars[0]);
    if (!mch_isdir(p) && (fd = fopen((char *)p, "r")) != NULL)
    {
	n = TRUE;
	fclose(fd);
    }
    else
	n = FALSE;

    retvar->var_val.var_number = n;
}
	    
/*
 * "getline(lnum)" function
 */
    static void
f_getline(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    linenr_t	lnum;
    char_u	*p;

    lnum = get_var_number(argvars[0]);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
	p = ml_get(lnum);
    else
	p = (char_u *)"";

    retvar->var_type = VAR_STRING;
    retvar->var_val.var_string = vim_strsave(p);
}

/*
 * "has()" function
 */
    static void
f_has(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    int		i;
    char_u	*name;
    int		n = FALSE;
    static char	*(has_list[]) =
    {
#ifdef MSDOS
# ifdef DJGPP
	"dos32",
# else
	"dos16",
# endif
#endif
#ifdef WIN32
	"win32",
#endif
#ifdef AMIGA
	"amiga",
# ifndef NO_ARP
	"arp",
# endif
#endif
#ifdef UNIX
	"unix",
#endif
#ifdef AUTOCMD
	"autocmd",
#endif
#if defined(SOME_BUILTIN_TCAPS) || defined(ALL_BUILTIN_TCAPS)
	"builtin_terms",
# ifdef ALL_BUILTIN_TCAPS
	"all_builtin_terms",
# endif
#endif
#ifdef CINDENT
	"cindent",
#endif
#ifdef DEBUG
	"debug",
#endif
#ifdef DIGRAPHS
	"digraphs",
#endif
#ifdef EMACS_TAGS
	"emacs_tags",
#endif
#ifdef FKMAP
	"farsi",
#endif
#if !defined(USE_SYSTEM) && defined(UNIX)
	"fork",
#endif
#ifdef USE_GUI
	"gui",
#endif
#ifdef USE_GUI_ATHENA
	"gui_athena",
#endif
#ifdef USE_GUI_MOTIF
	"gui_motif",
#endif
#ifdef USE_GUI_WIN32
	"gui_win32",
#endif
#ifdef INSERT_EXPAND
	"insert_expand",
#endif
#ifdef HAVE_LANGMAP
	"langmap",
#endif
#ifdef LISPINDENT
	"lispindent",
#endif
#ifdef HAVE_PYTHON
	"python",
#endif
#ifdef HAVE_PERL_INTERP
	"perl",
#endif
#ifdef RIGHTLEFT
	"rightleft",
#endif
#ifdef SMARTINDENT
	"smartindent",
#endif
#ifdef SYNTAX_HL
	"syntax",
#endif
#if defined(USE_SYSTEM) || !defined(UNIX)
	"system",
#endif
#ifdef TERMINFO
	"terminfo",
#endif
#ifdef HAVE_TGETENT
	"tgetent",
#endif
#ifdef VIMINFO
	"viminfo",
#endif
#ifdef WRITEBACKUP
	"writebackup",
#endif
#if defined(UNIX) && defined(WANT_X11) && defined(HAVE_X11)
	"x11",
#endif
	NULL
    };

    name = get_var_string(argvars[0]);
    for (i = 0; has_list[i] != NULL; ++i)
	if (STRICMP(name, has_list[i]) == 0)
	{
	    n = TRUE;
	    break;
	}

    if (n == FALSE)
    {
#ifdef USE_GUI
	if (STRICMP(name, "gui_running") == 0)
	{
	    if (gui.in_use || gui.starting)
		n = TRUE;
	}
#endif
#ifdef SYNTAX_HL
# ifdef USE_GUI
	else
# endif
	    if (STRICMP(name, "syntax_items") == 0)
	{
	    if (syntax_present(curbuf))
		n = TRUE;
	}
#endif
    }

    retvar->var_val.var_number = n;
}

/*
 * "highlight_exists()" function
 */
    static void
f_highlight_exists(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    retvar->var_val.var_number = highlight_exists(get_var_string(argvars[0]));
}

#ifdef HAVE_STRFTIME
/*
 * "strftime()" function
 */
    static void
f_strftime(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    char_u	result_buf[80];
    struct tm	*curtime;
    time_t	seconds;
    char_u	*p;

    p = get_var_string(argvars[0]);
    seconds = time(NULL);
    curtime = localtime(&seconds);
    (void)strftime((char *)result_buf, (size_t)80, (char *)p, curtime);

    retvar->var_type = VAR_STRING;
    retvar->var_val.var_string = vim_strsave(result_buf);
}
#endif

/*
 * "strlen()" function
 */
    static void
f_strlen(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    retvar->var_val.var_number = STRLEN(get_var_string(argvars[0]));
}

/*
 * "substr()" function
 */
    static void
f_substr(argvars, retvar)
    VAR	    *argvars;
    VAR	    retvar;
{
    char_u	*p;
    int		n;
    int		len;
    int		slen;

    p = get_var_string(argvars[0]);
    n = get_var_number(argvars[1]);
    len = get_var_number(argvars[2]);
    slen = STRLEN(p);
    if (n > slen)
	n = slen;
    if (n + len > slen)
	len = slen - n;

    retvar->var_type = VAR_STRING;
    retvar->var_val.var_string = vim_strnsave(p + n, len);
}

/*
 * Get the name of an environment variable.
 * Return a pointer to allocated memory.
 * Return NULL for error.
 * Advance "arg" to the first character after the name.
 */
    static char_u *
get_env_name(arg)
    char_u	**arg;
{
    char_u	*p;
    char_u	*name;

    for (p = *arg; isidchar(*p); ++p)
	;
    if (p == *arg)	    /* no name found */
	return NULL;

    /*
     * Copy the name into allocated memory and get the value.
     */
    name = vim_strnsave(*arg, (int)(p - *arg));
    *arg = p;
    return name;
}

/*
 * Get the name of a function or internal variable.
 * Returns a pointer to allocated memory, or NULL if something is wrong.
 */
    static char_u *
get_ident(arg)
    char_u	**arg;
{
    char_u	*p;
    char_u	*name;

    /*
     * Find the end of the name;
     */
    for (p = *arg; isnamechar(*p); ++p)
	;
    if (p == *arg)	    /* no name found */
	return NULL;

    /*
     * Copy the name into allocated memory.
     */
    name = vim_strnsave(*arg, (int)(p - *arg));
    *arg = skipwhite(p);

    return name;
}

    static int
isnamechar(c)
    int	    c;
{
    return (isalpha(c) || isdigit(c) || c == '_');
}

/*
 * Allocate a variable for an internal variable.
 * The "name" argument must be in allocated memory and will be eaten.
 * Returns a new variable, or NULL if something is wrong.
 */
    static VAR
get_var_var(name, error_msg)
    char_u	*name;
    int		error_msg;
{
    VAR		retvar;
    int		type = VAR_UNKNOWN;
    long	number = 1;
    char_u	*string = NULL;
    int		i;

    if (STRCMP(name, "version") == 0)
    {
	type = VAR_NUMBER;
	number = get_version();
    }

    /*
     * Check for user-defined variables.
     */
    else
    {
	i = find_var(name);
	if (i >= 0)
	{
	    type = VAR_ENTRY(i).var_type;
	    if (type == VAR_NUMBER)
		number = VAR_ENTRY(i).var_val.var_number;
	    else if (VAR_ENTRY(i).var_val.var_string == NULL)
		string = NULL;
	    else
		string = vim_strsave(VAR_ENTRY(i).var_val.var_string);
	}
    }

    if (type == VAR_UNKNOWN)
    {
	if (error_msg)
	    EMSG2("Undefined variable: %s", name);
	retvar = NULL;
    }
    else
    {
	retvar = alloc_var();
	if (retvar == NULL)
	    vim_free(string);
	else
	{
	    retvar->var_type = type;
	    if (type == VAR_NUMBER)
		retvar->var_val.var_number = number;
	    else
		retvar->var_val.var_string = string;
	}
    }

    vim_free(name);

    return retvar;
}

/*
 * Allocate memory for a variable, and make it emtpy (0 or NULL value).
 */
    static VAR
alloc_var()
{
    return (VAR)alloc_clear((unsigned)sizeof(struct var));
}

/*
 * Free the memory for a variable.
 */
    static void
free_var(varp)
    VAR	    varp;
{
    if (varp != NULL)
    {
	if (varp->var_type == VAR_STRING)
	    vim_free(varp->var_val.var_string);
	vim_free(varp->var_name);
	vim_free(varp);
    }
}

/*
 * Get the number value of a variable.
 * If it is a String variable, use vim_str2nr().
 */
    static long
get_var_number(varp)
    VAR	    varp;
{
    if (varp->var_type == VAR_NUMBER)
	return (long)(varp->var_val.var_number);
    else if (varp->var_val.var_string == NULL)
	return 0L;
    else
	return vim_str2nr(varp->var_val.var_string, NULL, NULL, TRUE, TRUE);
}

/*
 * Get the string value of a variable.
 * If it is a Number variable, the number is converted into a string.
 * get_var_string() uses a single, static buffer.  You can only use it once!
 * get_var_string_buf() uses a given buffer.
 * If the String variable has never been set, return an empty string.
 * Never returns NULL;
 */
    static char_u *
get_var_string(varp)
    VAR	    varp;
{
    static char_u   mybuf[NUMBUFLEN];

    return get_var_string_buf(varp, mybuf);
}

    static char_u *
get_var_string_buf(varp, buf)
    VAR	    varp;
    char_u  *buf;
{
    if (varp->var_type == VAR_NUMBER)
    {
	sprintf((char *)buf, "%ld", (long)varp->var_val.var_number);
	return buf;
    }
    else if (varp->var_val.var_string == NULL)
	return (char_u *)"";
    else
	return varp->var_val.var_string;
}

/*
 * Find variable "name" in the list of variables.
 * Return its index if found, -1 if not found.
 */
    static int
find_var(name)
    char_u	*name;
{
    int	    i;

    /*
     * Initialize the variables for use.
     */
    variables.ga_itemsize = sizeof(struct var);
    variables.ga_growsize = 4;

    for (i = variables.ga_len; --i >= 0; )
	if (VAR_ENTRY(i).var_name != NULL
				  && STRCMP(VAR_ENTRY(i).var_name, name) == 0)
	    break;
    return i;
}

/*
 * List the value of one internal variable.
 */
    static void
list_one_var(i)
    int	    i;
{
    int		endcol = 15;

    if (Columns <= 15)
	endcol = Columns - 1;

    msg(VAR_ENTRY(i).var_name);
    msg_putchar(' ');
    while (msg_col < endcol)
	msg_putchar(' ');
    if (VAR_ENTRY(i).var_type == VAR_NUMBER)
	msg_putchar('#');
    else
	msg_putchar(' ');
    msg_puts(get_var_string(&(VAR_ENTRY(i))));
}

/*
 * Set variable "name" to value in "varp".
 * If the variable already exists, the value is updated.
 * Otherwise the variable is created.
 */
    static void
set_var(name, varp)
    char_u	*name;
    VAR		varp;
{
    int	    i;

    i = find_var(name);
    if (i >= 0)		    /* existing variable, may need to free string */
    {
	if (VAR_ENTRY(i).var_type == VAR_STRING)
	    vim_free(VAR_ENTRY(i).var_val.var_string);
    }
    else		    /* add a new variable */
    {
	/* Try to use an empty entry */
	for (i = variables.ga_len; --i >= 0; )
	    if (VAR_ENTRY(i).var_name == NULL)
		break;
	if (i < 0)	    /* need to allocated more room */
	{
	    if (ga_grow(&variables, 1) == FAIL)
		return;
	    i = variables.ga_len;
	}
	if ((VAR_ENTRY(i).var_name = vim_strsave(name)) == NULL)
	    return;
	if (i == variables.ga_len)
	{
	    ++variables.ga_len;
	    --variables.ga_room;
	}
    }

    VAR_ENTRY(i).var_type = varp->var_type;
    if (varp->var_type == VAR_STRING)
	VAR_ENTRY(i).var_val.var_string = vim_strsave(get_var_string(varp));
    else
	VAR_ENTRY(i).var_val.var_number = varp->var_val.var_number;
}

/*
 * Implementation of
 * ":echo expr1 .."	print each argument separated with a space, add a
 *			newline at the end.
 * ":echon expr1 .."	print each argument plain.
 */
    void
do_echo(arg, echo)
    char_u	*arg;
    int		echo;	    /* ":echo" command */
{
    VAR		varp;
    char_u	*p;

    if (echo)
	msg_start();
    while (*arg)
    {
	varp = eval1(&arg);
	if (varp == NULL)
	    break;
	for (p = get_var_string(varp); *p != NUL; ++p)
	    if (*p == '\n' || *p == '\r' || *p == TAB)
		msg_putchar(*p);
	    else
		msg_puts(transchar(*p));
	arg = skipwhite(arg);
	if (*arg && echo)
	    msg_putchar(' ');
    }
    msg_clr_eos();
    if (echo)
	msg_end();
}

/*
 * Implementation of
 * ":execute expr1 .."	execute the result of an expression.
 */
    void
do_execute(arg, getline, cookie)
    char_u	*arg;
    char_u	*(*getline) __ARGS((int, void *, int));
    void	*cookie;		/* argument for getline() */
{
    VAR		varp = NULL;
    char_u	*result = NULL;
    char_u	*new;
    char_u	*p;

    while (*arg)
    {
	varp = eval1(&arg);
	if (varp == NULL)
	    break;
	p = get_var_string(varp);
	if (result == NULL)
	    result = vim_strsave(p);
	else
	{
	    /*
	     * Append the new part after the processed part.
	     */
	    new = vim_strnsave(result, (int)(STRLEN(p) + STRLEN(result) + 2));
	    if (new == NULL)
	    {
		varp = NULL;
		break;
	    }
	    STRCAT(new, " ");
	    STRCAT(new, p);
	    vim_free(result);
	    result = new;
	}

	arg = skipwhite(arg);
    }

    if (varp != NULL && result != NULL)
	do_cmdline(result, getline, cookie, DOCMD_NOWAIT|DOCMD_VERBOSE);

    vim_free(result);
}

#endif /* WANT_EVAL */
