/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * eval.c -- constraint evaluation module
 *
 * this module evaluates constraints.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "alloc.h"
#include "out.h"
#include "stable.h"
#include "literals.h"
#include "lut.h"
#include "tree.h"
#include "ptree.h"
#include "itree.h"
#include "ipath.h"
#include "eval.h"
#include "config.h"
#include "platform.h"
#include "fme.h"
#include "stats.h"

static struct node *eval_dup(struct node *np, struct lut *ex,
    struct node *epnames[]);
static int check_expr_args(struct evalue *lp, struct evalue *rp,
    enum datatype dtype, struct node *np);

/*
 * begins_with -- return true if rhs path begins with everything in lhs path
 */
static int
begins_with(struct node *lhs, struct node *rhs)
{
	int lnum;
	int rnum;

	if (lhs == NULL)
		return (1);	/* yep -- it all matched */

	if (rhs == NULL)
		return (0);	/* nope, ran out of rhs first */

	ASSERTeq(lhs->t, T_NAME, ptree_nodetype2str);
	ASSERTeq(rhs->t, T_NAME, ptree_nodetype2str);

	if (lhs->u.name.s != rhs->u.name.s)
		return (0);	/* nope, different component names */

	if (lhs->u.name.child && lhs->u.name.child->t == T_NUM)
		lnum = (int)lhs->u.name.child->u.ull;
	else
		out(O_DIE, "begins_with: unexpected lhs child");

	if (rhs->u.name.child && rhs->u.name.child->t == T_NUM)
		rnum = (int)rhs->u.name.child->u.ull;
	else
		out(O_DIE, "begins_with: unexpected rhs child");

	if (lnum != rnum)
		return (0);	/* nope, instance numbers were different */

	return (begins_with(lhs->u.name.next, rhs->u.name.next));
}

/*
 * evaluate a variety of functions and place result in valuep.  return 1 if
 * function evaluation was successful; 0 if otherwise (e.g., the case of an
 * invalid argument to the function)
 */
/*ARGSUSED*/
static int
eval_func(struct node *funcnp, struct lut *ex, struct node *epnames[],
    struct node *np, struct lut **globals,
    struct config *croot, struct arrow *arrowp, int try, struct evalue *valuep)
{
	const char *funcname = funcnp->u.func.s;

	if (funcname == L_within) {
		/* within()'s are not really constraints -- always true */
		valuep->t = UINT64;
		valuep->v = 1;
		return (1);
	} else if (funcname == L_is_under) {
		struct node *lhs;
		struct node *rhs;

		if (np->u.expr.left->t == T_NAME)
			lhs = np->u.expr.left;
		else if (np->u.expr.left->u.func.s == L_fru)
			lhs = eval_fru(np->u.expr.left->u.func.arglist);
		else if (np->u.expr.left->u.func.s == L_asru)
			lhs = eval_asru(np->u.expr.left->u.func.arglist);
		else
			out(O_DIE, "is_under: unexpected lhs type: %s",
			    ptree_nodetype2str(np->u.expr.left->t));

		if (np->u.expr.right->t == T_NAME)
			rhs = np->u.expr.right;
		else if (np->u.expr.right->u.func.s == L_fru)
			rhs = eval_fru(np->u.expr.right->u.func.arglist);
		else if (np->u.expr.right->u.func.s == L_asru)
			rhs = eval_asru(np->u.expr.right->u.func.arglist);
		else
			out(O_DIE, "is_under: unexpected rhs type: %s",
			    ptree_nodetype2str(np->u.expr.right->t));

		/* eval_dup will expand wildcards, iterators, etc... */
		lhs = eval_dup(lhs, ex, epnames);
		rhs = eval_dup(rhs, ex, epnames);
		valuep->t = UINT64;
		valuep->v = begins_with(lhs, rhs);

		out(O_ALTFP|O_VERB2|O_NONL, "eval_func:is_under(");
		ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, lhs);
		out(O_ALTFP|O_VERB2|O_NONL, ",");
		ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, rhs);
		out(O_ALTFP|O_VERB2, ") returned %d", (int)valuep->v);

		tree_free(lhs);
		tree_free(rhs);

		return (1);
	} else if (funcname == L_confprop || funcname == L_confprop_defined) {
		struct config *cp;
		struct node *lhs;
		char *path;
		const char *s;

		if (np->u.expr.left->u.func.s == L_fru)
			lhs = eval_fru(np->u.expr.left->u.func.arglist);
		else if (np->u.expr.left->u.func.s == L_asru)
			lhs = eval_asru(np->u.expr.left->u.func.arglist);
		else
			out(O_DIE, "%s: unexpected lhs type: %s",
			    funcname, ptree_nodetype2str(np->u.expr.left->t));

		/* for now s will point to a quote [see addconfigprop()] */
		ASSERT(np->u.expr.right->t == T_QUOTE);

		/* eval_dup will expand wildcards, iterators, etc... */
		lhs = eval_dup(lhs, ex, epnames);
		path = ipath2str(NULL, ipath(lhs));
		cp = config_lookup(croot, path, 0);
		tree_free(lhs);
		if (cp == NULL) {
			out(O_ALTFP|O_VERB3, "%s: path %s not found",
			    funcname, path);
			FREE((void *)path);
			if (funcname == L_confprop_defined) {
				valuep->v = 0;
				valuep->t = UINT64;
				return (1);
			} else {
				return (0);
			}
		}
		s = config_getprop(cp, np->u.expr.right->u.quote.s);
		if (s == NULL) {
			out(O_ALTFP|O_VERB3, "%s: \"%s\" not found for path %s",
			    funcname, np->u.expr.right->u.quote.s, path);
			FREE((void *)path);
			if (funcname == L_confprop_defined) {
				valuep->v = 0;
				valuep->t = UINT64;
				return (1);
			} else {
				return (0);
			}
		}

		if (funcname == L_confprop) {
			valuep->v = (uintptr_t)stable(s);
			valuep->t = STRING;
			out(O_ALTFP|O_VERB3, "%s(\"%s\", \"%s\") = \"%s\"",
			    funcname, path, np->u.expr.right->u.quote.s,
			    (char *)(uintptr_t)valuep->v);
		} else {
			valuep->v = 1;
			valuep->t = UINT64;
		}
		FREE((void *)path);
		return (1);
	}

	if (try)
		return (0);

	if (funcname == L_fru) {
		valuep->t = NODEPTR;
		valuep->v = (uintptr_t)eval_fru(np);
		return (1);
	} else if (funcname == L_asru) {
		valuep->t = NODEPTR;
		valuep->v = (uintptr_t)eval_asru(np);
		return (1);
	} else if (funcname == L_defined) {
		ASSERTeq(np->t, T_GLOBID, ptree_nodetype2str);
		valuep->t = UINT64;
		valuep->v = (lut_lookup(*globals,
		    (void *)np->u.globid.s, NULL) != NULL);
		return (1);
	} else if (funcname == L_call) {
		return (! platform_call(np, globals, croot, arrowp, valuep));
	} else if (funcname == L_is_connected) {
		return (! config_is_connected(np, croot, valuep));
	} else if (funcname == L_is_on) {
		return (! config_is_on(np, croot, valuep));
	} else if (funcname == L_is_present) {
		return (! config_is_present(np, croot, valuep));
	} else if (funcname == L_is_type) {
		return (! config_is_type(np, croot, valuep));
	} else if (funcname == L_envprop) {
		outfl(O_DIE, np->file, np->line,
		    "eval_func: %s not yet supported", funcname);
	} else if (funcname == L_payloadprop) {
		outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line,
		    "payloadprop(\"%s\") ", np->u.quote.s);

		if (platform_payloadprop(np, valuep)) {
			/* platform_payloadprop() returned false */
			out(O_ALTFP|O_VERB, "payloadprop \"%s\" not found.",
			    np->u.quote.s);
			return (0);
		} else {
			switch (valuep->t) {
			case UINT64:
			case NODEPTR:
				out(O_ALTFP|O_VERB2, "found: %llu", valuep->v);
				break;
			case STRING:
				out(O_ALTFP|O_VERB2, "found: \"%s\"",
				    (char *)(uintptr_t)valuep->v);
				break;
			default:
				out(O_ALTFP|O_VERB2, "found: undefined");
				break;
			}
			return (1);
		}
	} else if (funcname == L_setpayloadprop) {
		struct evalue *payloadvalp;

		ASSERTinfo(np->t == T_LIST, ptree_nodetype2str(np->t));
		ASSERTinfo(np->u.expr.left->t == T_QUOTE,
		    ptree_nodetype2str(np->u.expr.left->t));

		outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line,
		    "setpayloadprop: %s: %s=",
		    arrowp->tail->myevent->enode->u.event.ename->u.name.s,
		    np->u.expr.left->u.quote.s);
		ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np->u.expr.right);

		/*
		 * allocate a struct evalue to hold the payload property's
		 * value, unless we've been here already, in which case we
		 * might calculate a different value, but we'll store it
		 * in the already-allocated struct evalue.
		 */
		if ((payloadvalp = (struct evalue *)lut_lookup(
		    arrowp->tail->myevent->payloadprops,
		    (void *)np->u.expr.left->u.quote.s, NULL)) == NULL) {
			payloadvalp = MALLOC(sizeof (*payloadvalp));
		}

		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
		    arrowp, try, payloadvalp)) {
			out(O_ALTFP|O_VERB2, " (cannot eval, using zero)");
			payloadvalp->t = UINT64;
			payloadvalp->v = 0;
		} else {
			if (payloadvalp->t == UINT64)
				out(O_ALTFP|O_VERB2,
				    " (%llu)", payloadvalp->v);
			else
				out(O_ALTFP|O_VERB2, " (\"%s\")",
				    (char *)(uintptr_t)payloadvalp->v);
		}

		/* add to table of payload properties for current problem */
		arrowp->tail->myevent->payloadprops =
		    lut_add(arrowp->tail->myevent->payloadprops,
		    (void *)np->u.expr.left->u.quote.s,
		    (void *)payloadvalp, NULL);

		/* function is always true */
		valuep->t = UINT64;
		valuep->v = 1;
		return (1);
	} else if (funcname == L_payloadprop_defined) {
		outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line,
		    "payloadprop_defined(\"%s\") ", np->u.quote.s);

		if (platform_payloadprop(np, NULL)) {
			/* platform_payloadprop() returned false */
			valuep->v = 0;
			out(O_ALTFP|O_VERB2, "payloadprop_defined: \"%s\" "
			    "not defined.", np->u.quote.s);
		} else {
			valuep->v = 1;
			out(O_ALTFP|O_VERB2, "found.");
		}
		valuep->t = UINT64;
		return (1);
	} else if (funcname == L_payloadprop_contains) {
		int nvals;
		struct evalue *vals;
		struct evalue cmpval;

		ASSERTinfo(np->t == T_LIST, ptree_nodetype2str(np->t));
		ASSERTinfo(np->u.expr.left->t == T_QUOTE,
		    ptree_nodetype2str(np->u.expr.left->t));

		outfl(O_ALTFP|O_VERB2|O_NONL, np->file, np->line,
		    "payloadprop_contains(\"%s\", ",
		    np->u.expr.left->u.quote.s);
		ptree_name_iter(O_ALTFP|O_VERB2|O_NONL, np->u.expr.right);
		out(O_ALTFP|O_VERB2|O_NONL, ") ");

		/* evaluate the expression we're comparing against */
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
		    arrowp, try, &cmpval)) {
			out(O_ALTFP|O_VERB2|O_NONL,
			    "(cannot eval, using zero) ");
			cmpval.t = UINT64;
			cmpval.v = 0;
		} else {
			switch (cmpval.t) {
			case UNDEFINED:
				out(O_ALTFP|O_VERB2, "(undefined type)");
				break;

			case UINT64:
				out(O_ALTFP|O_VERB2,
				    "(%llu) ", cmpval.v);
				break;

			case STRING:
				out(O_ALTFP|O_VERB2,
				    "(\"%s\") ", (char *)(uintptr_t)cmpval.v);
				break;

			case NODEPTR:
				out(O_ALTFP|O_VERB2|O_NONL, "(");
				ptree_name_iter(O_ALTFP|O_VERB2|O_NONL,
				    (struct node *)(uintptr_t)(cmpval.v));
				out(O_ALTFP|O_VERB2, ") ");
				break;
			}
		}

		/* get the payload values and check for a match */
		vals = platform_payloadprop_values(np->u.expr.left->u.quote.s,
		    &nvals);
		valuep->t = UINT64;
		valuep->v = 0;
		if (nvals == 0) {
			out(O_ALTFP|O_VERB2, "not found.");
		} else {
			struct evalue preval;
			int i;

			out(O_ALTFP|O_VERB2|O_NONL, "found %d values ", nvals);

			for (i = 0; i < nvals; i++) {

				preval.t = vals[i].t;
				preval.v = vals[i].v;

				if (check_expr_args(&vals[i], &cmpval,
				    UNDEFINED, np))
					continue;

				/*
				 * If we auto-converted the value to a
				 * string, we need to free the
				 * original tree value.
				 */
				if (preval.t == NODEPTR &&
				    ((struct node *)(uintptr_t)(preval.v))->t ==
				    T_NAME) {
					tree_free((struct node *)(uintptr_t)
					    preval.v);
				}

				if (vals[i].v == cmpval.v) {
					valuep->v = 1;
					break;
				}
			}

			if (valuep->v)
				out(O_ALTFP|O_VERB2, "match.");
			else
				out(O_ALTFP|O_VERB2, "no match.");

			for (i = 0; i < nvals; i++) {
				if (vals[i].t == NODEPTR) {
					tree_free((struct node *)(uintptr_t)
					    vals[i].v);
					break;
				}
			}
			FREE(vals);
		}
		return (1);
	} else if (funcname == L_confcall) {
		return (!platform_confcall(np, globals, croot, arrowp, valuep));
	} else if (funcname == L_count) {
		struct stats *statp;
		struct istat_entry ent;

		ASSERTinfo(np->t == T_EVENT, ptree_nodetype2str(np->t));

		ent.ename = np->u.event.ename->u.name.s;
		ent.ipath = ipath(np->u.event.epname);

		valuep->t = UINT64;
		if ((statp = (struct stats *)
		    lut_lookup(Istats, &ent, (lut_cmp)istat_cmp)) == NULL)
			valuep->v = 0;
		else
			valuep->v = stats_counter_value(statp);

		return (1);
	} else
		outfl(O_DIE, np->file, np->line,
		    "eval_func: unexpected func: %s", funcname);
	/*NOTREACHED*/
	return (0);
}

static struct node *
eval_wildcardedname(struct node *np, struct lut *ex, struct node *epnames[])
{
	struct node *npstart, *npend, *npref, *newnp;
	struct node *np1, *np2, *retp;
	int i;

	if (epnames == NULL || epnames[0] == NULL)
		return (NULL);

	for (i = 0; epnames[i] != NULL; i++) {
		if (tree_namecmp(np, epnames[i]) == 0)
			return (NULL);
	}

	/*
	 * get to this point if np does not match any of the entries in
	 * epnames.  check if np is a path that must preceded by a wildcard
	 * portion.  for this case we must first determine which epnames[]
	 * entry should be used for wildcarding.
	 */
	npstart = NULL;
	for (i = 0; epnames[i] != NULL; i++) {
		for (npref = epnames[i]; npref; npref = npref->u.name.next) {
			if (npref->u.name.s == np->u.name.s) {
				for (np1 = npref, np2 = np;
				    np1 != NULL && np2 != NULL;
				    np1 = np1->u.name.next,
					    np2 = np2->u.name.next) {
					if (np1->u.name.s != np2->u.name.s)
						break;
				}
				if (np2 == NULL) {
					npstart = epnames[i];
					npend = npref;
					if (np1 == NULL)
						break;
				}
			}
		}

		if (npstart != NULL)
			break;
	}

	if (npstart == NULL) {
		/* no match; np is not a path to be wildcarded */
		return (NULL);
	}

	/*
	 * dup (npstart -- npend) which is the wildcarded portion.  all
	 * children should be T_NUMs.
	 */
	retp = NULL;
	for (npref = npstart;
	    ! (npref == NULL || npref == npend);
	    npref = npref->u.name.next) {
		newnp = newnode(T_NAME, np->file, np->line);

		newnp->u.name.t = npref->u.name.t;
		newnp->u.name.s = npref->u.name.s;
		newnp->u.name.last = newnp;
		newnp->u.name.it = npref->u.name.it;
		newnp->u.name.cp = npref->u.name.cp;

		ASSERT(npref->u.name.child != NULL);
		ASSERT(npref->u.name.child->t == T_NUM);
		newnp->u.name.child = newnode(T_NUM, np->file, np->line);
		newnp->u.name.child->u.ull = npref->u.name.child->u.ull;

		if (retp == NULL) {
			retp = newnp;
		} else {
			retp->u.name.last->u.name.next = newnp;
			retp->u.name.last = newnp;
		}
	}

	ASSERT(retp != NULL);

	/* now append the nonwildcarded portion */
	retp = tree_name_append(retp, eval_dup(np, ex, NULL));

	return (retp);
}

static struct node *
eval_dup(struct node *np, struct lut *ex, struct node *epnames[])
{
	struct node *newnp;

	if (np == NULL)
		return (NULL);

	switch (np->t) {
	case T_GLOBID:
		return (tree_globid(np->u.globid.s, np->file, np->line));

	case T_ASSIGN:
	case T_CONDIF:
	case T_CONDELSE:
	case T_NE:
	case T_EQ:
	case T_LT:
	case T_LE:
	case T_GT:
	case T_GE:
	case T_BITAND:
	case T_BITOR:
	case T_BITXOR:
	case T_BITNOT:
	case T_LSHIFT:
	case T_RSHIFT:
	case T_LIST:
	case T_AND:
	case T_OR:
	case T_NOT:
	case T_ADD:
	case T_SUB:
	case T_MUL:
	case T_DIV:
	case T_MOD:
		return (tree_expr(np->t,
				    eval_dup(np->u.expr.left, ex, epnames),
				    eval_dup(np->u.expr.right, ex, epnames)));

	case T_NAME: {
		struct iterinfo *iterinfop;
		struct node *newchild = NULL;

		iterinfop = lut_lookup(ex, (void *)np->u.name.s, NULL);
		if (iterinfop != NULL) {
			/* explicit iterator; not part of pathname */
			newnp = newnode(T_NUM, np->file, np->line);
			newnp->u.ull = iterinfop->num;
			return (newnp);
		}

		/* see if np is a path with wildcard portion */
		newnp = eval_wildcardedname(np, ex, epnames);
		if (newnp != NULL)
			return (newnp);

		/* turn off wildcarding for child */
		newchild = eval_dup(np->u.name.child, ex, NULL);

		if (newchild != NULL) {
			if (newchild->t != T_NUM) {
				/*
				 * not a number, eh?  we must resolve this
				 * to a number.
				 */
				struct evalue value;

				if (eval_expr(newchild, ex, epnames,
				    NULL, NULL, NULL, 1, &value) == 0 ||
				    value.t != UINT64) {
					outfl(O_DIE, np->file, np->line,
					    "eval_dup: could not resolve "
					    "iterator of %s", np->u.name.s);
				}

				tree_free(newchild);
				newchild = newnode(T_NUM, np->file, np->line);
				newchild->u.ull = value.v;
			}

			newnp = newnode(np->t, np->file, np->line);
			newnp->u.name.s = np->u.name.s;
			newnp->u.name.it = np->u.name.it;
			newnp->u.name.cp = np->u.name.cp;

			newnp->u.name.last = newnp;
			newnp->u.name.child = newchild;

			if (np->u.name.next != NULL) {
				/* turn off wildcarding for next */
				return (tree_name_append(newnp,
					eval_dup(np->u.name.next, ex, NULL)));
			} else {
				return (newnp);
			}
		} else {
			outfl(O_DIE, np->file, np->line,
			    "eval_dup: internal error: \"%s\" is neither "
			    "an iterator nor a pathname", np->u.name.s);
		}
		/*NOTREACHED*/
		break;
	}

	case T_EVENT:
		newnp = newnode(T_NAME, np->file, np->line);

		newnp->u.name.t = np->u.event.ename->u.name.t;
		newnp->u.name.s = np->u.event.ename->u.name.s;
		newnp->u.name.it = np->u.event.ename->u.name.it;
		newnp->u.name.last = newnp;

		return (tree_event(newnp,
		    eval_dup(np->u.event.epname, ex, epnames),
		    eval_dup(np->u.event.eexprlist, ex, epnames)));

	case T_FUNC:
		return (tree_func(np->u.func.s,
		    eval_dup(np->u.func.arglist, ex, epnames),
		    np->file, np->line));

	case T_QUOTE:
		newnp = newnode(T_QUOTE, np->file, np->line);
		newnp->u.quote.s = np->u.quote.s;
		return (newnp);

	case T_NUM:
		newnp = newnode(T_NUM, np->file, np->line);
		newnp->u.ull = np->u.ull;
		return (newnp);

	default:
		outfl(O_DIE, np->file, np->line,
		    "eval_dup: unexpected node type: %s",
		    ptree_nodetype2str(np->t));
	}
	/*NOTREACHED*/
	return (0);
}

/*
 * eval_potential -- see if constraint is potentially true
 *
 * this function is used at instance tree creation time to see if
 * any constraints are already known to be false.  if this function
 * returns false, then the constraint will always be false and there's
 * no need to include the propagation arrow in the instance tree.
 *
 * if this routine returns true, either the constraint is known to
 * be always true (so there's no point in attaching the constraint
 * to the propagation arrow in the instance tree), or the constraint
 * contains "deferred" expressions like global variables or poller calls
 * and so it must be evaluated during calls to fme_eval().  in this last
 * case, where a constraint needs to be attached to the propagation arrow
 * in the instance tree, this routine returns a newly created constraint
 * in *newc where all the non-deferred things have been filled in.
 *
 * so in summary:
 *
 *	return of false: constraint can never be true, *newc will be NULL.
 *
 *	return of true with *newc unchanged: constraint will always be true.
 *
 *	return of true with *newc changed: use new constraint in *newc.
 *
 * the lookup table for all explicit iterators, ex, is passed in.
 *
 * *newc can either be NULL on entry, or if can contain constraints from
 * previous calls to eval_potential() (i.e. for building up an instance
 * tree constraint from several potential constraints).  if *newc already
 * contains constraints, anything added to it will be joined by adding
 * a T_AND node at the top of *newc.
 */
int
eval_potential(struct node *np, struct lut *ex, struct node *epnames[],
	    struct node **newc, struct config *croot)
{
	struct node *newnp;
	struct evalue value;

	if (eval_expr(np, ex, epnames, NULL, croot, NULL, 1, &value) == 0) {
		/*
		 * couldn't eval expression because
		 * it contains deferred items.  make
		 * a duplicate expression with all the
		 * non-deferred items expanded.
		 */
		newnp = eval_dup(np, ex, epnames);

		if (*newc == NULL) {
			/*
			 * constraint is potentially true if deferred
			 * expression in newnp is true.  *newc was NULL
			 * so new constraint is just the one in newnp.
			 */
			*newc = newnp;
			return (1);
		} else {
			/*
			 * constraint is potentially true if deferred
			 * expression in newnp is true.  *newc already
			 * contained a constraint so add an AND with the
			 * constraint in newnp.
			 */
			*newc = tree_expr(T_AND, *newc, newnp);
			return (1);
		}
	} else if (value.t == UNDEFINED) {
		/* constraint can never be true */
		return (0);
	} else if (value.t == UINT64 && value.v == 0) {
		/* constraint can never be true */
		return (0);
	} else {
		/* constraint is always true (nothing deferred to eval) */
		return (1);
	}
}

static int
check_expr_args(struct evalue *lp, struct evalue *rp, enum datatype dtype,
		struct node *np)
{
	/* auto-convert T_NAMES to strings */
	if (lp->t == NODEPTR && ((struct node *)(uintptr_t)(lp->v))->t ==
	    T_NAME) {
		char *s = ipath2str(NULL,
		    ipath((struct node *)(uintptr_t)lp->v));
		lp->t = STRING;
		lp->v = (uintptr_t)stable(s);
		FREE(s);
		out(O_ALTFP|O_VERB2, "convert lhs path to \"%s\"",
		    (char *)(uintptr_t)lp->v);
	}
	if (rp != NULL &&
	    rp->t == NODEPTR && ((struct node *)(uintptr_t)(rp->v))->t ==
	    T_NAME) {
		char *s = ipath2str(NULL,
		    ipath((struct node *)(uintptr_t)rp->v));
		rp->t = STRING;
		rp->v = (uintptr_t)stable(s);
		FREE(s);
		out(O_ALTFP|O_VERB2, "convert rhs path to \"%s\"",
		    (char *)(uintptr_t)rp->v);
	}

	/* auto-convert strings to numbers */
	if (dtype == UINT64) {
		if (lp->t == STRING) {
			lp->t = UINT64;
			lp->v = strtoull((char *)(uintptr_t)lp->v, NULL, 0);
		}
		if (rp != NULL && rp->t == STRING) {
			rp->t = UINT64;
			rp->v = strtoull((char *)(uintptr_t)rp->v, NULL, 0);
		}
	}

	if (dtype != UNDEFINED && lp->t != dtype) {
		outfl(O_OK, np->file, np->line,
			"invalid datatype of argument for operation %s",
			ptree_nodetype2str(np->t));
		return (1);
	}

	if (rp != NULL && lp->t != rp->t) {
		outfl(O_OK, np->file, np->line,
			"mismatch in datatype of arguments for operation %s",
			ptree_nodetype2str(np->t));
		return (1);
	}

	return (0);
}

/*
 * eval_expr -- evaluate expression into *valuep
 *
 * the meaning of the return value depends on the input value of try.
 *
 * for try == 1: if any deferred items are encounted, bail out and return
 * false.  returns true if we made it through entire expression without
 * hitting any deferred items.
 *
 * for try == 0: return true if all operations were performed successfully.
 * return false if otherwise.  for example, any of the following conditions
 * will result in a false return value:
 *   - attempted use of an uninitialized global variable
 *   - failure in function evaluation
 *   - illegal arithmetic operation (argument out of range)
 */
int
eval_expr(struct node *np, struct lut *ex, struct node *epnames[],
	struct lut **globals, struct config *croot, struct arrow *arrowp,
	int try, struct evalue *valuep)
{
	struct evalue *gval;
	struct evalue lval;
	struct evalue rval;

	if (np == NULL) {
		valuep->t = UINT64;
		valuep->v = 1;	/* no constraint means "true" */
		return (1);
	}

	valuep->t = UNDEFINED;

	switch (np->t) {
	case T_GLOBID:
		if (try)
			return (0);

		/*
		 * only handle case of getting (and not setting) the value
		 * of a global variable
		 */
		gval = lut_lookup(*globals, (void *)np->u.globid.s, NULL);
		if (gval == NULL) {
			valuep->t = UNDEFINED;
			return (0);
		} else {
			valuep->t = gval->t;
			valuep->v = gval->v;
			return (1);
		}

	case T_ASSIGN:
		if (try)
			return (0);

		/*
		 * first evaluate rhs, then try to store value in lhs which
		 * should be a global variable
		 */
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
			    arrowp, try, &rval))
			return (0);

		ASSERT(np->u.expr.left->t == T_GLOBID);
		gval = lut_lookup(*globals,
				(void *)np->u.expr.left->u.globid.s, NULL);

		if (gval == NULL) {
			gval = MALLOC(sizeof (*gval));
			*globals = lut_add(*globals,
					(void *) np->u.expr.left->u.globid.s,
					gval, NULL);
		}

		gval->t = rval.t;
		gval->v = rval.v;

		if (gval->t == UINT64) {
			out(O_ALTFP|O_VERB2,
			    "assign $%s=%llu",
			    np->u.expr.left->u.globid.s, gval->v);
		} else {
			out(O_ALTFP|O_VERB2,
			    "assign $%s=\"%s\"",
			    np->u.expr.left->u.globid.s,
			    (char *)(uintptr_t)gval->v);
		}

		/*
		 * but always return true -- an assignment should not
		 * cause a constraint to be false.
		 */
		valuep->t = UINT64;
		valuep->v = 1;
		return (1);

	case T_EQ:
#define	IMPLICIT_ASSIGN_IN_EQ
#ifdef IMPLICIT_ASSIGN_IN_EQ
		/*
		 * if lhs is an uninitialized global variable, perform
		 * an assignment.
		 *
		 * one insidious side effect of implicit assignment is
		 * that the "==" operator does not return a Boolean if
		 * implicit assignment was performed.
		 */
		if (try == 0 &&
		    np->u.expr.left->t == T_GLOBID &&
		    (gval = lut_lookup(*globals,
			(void *)np->u.expr.left->u.globid.s, NULL)) == NULL) {
			if (!eval_expr(np->u.expr.right, ex, epnames, globals,
					croot, arrowp, try, &rval))
				return (0);

			gval = MALLOC(sizeof (*gval));
			*globals = lut_add(*globals,
					(void *) np->u.expr.left->u.globid.s,
					gval, NULL);

			gval->t = rval.t;
			gval->v = rval.v;
			valuep->t = rval.t;
			valuep->v = rval.v;
			return (1);
		}
#endif  /* IMPLICIT_ASSIGN_IN_EQ */

		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UNDEFINED, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v == rval.v);
		return (1);

	case T_LT:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v < rval.v);
		return (1);

	case T_LE:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v <= rval.v);
		return (1);

	case T_GT:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v > rval.v);
		return (1);

	case T_GE:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v >= rval.v);
		return (1);

	case T_BITAND:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = lval.t;
		valuep->v = (lval.v & rval.v);
		return (1);

	case T_BITOR:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = lval.t;
		valuep->v = (lval.v | rval.v);
		return (1);

	case T_BITXOR:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = lval.t;
		valuep->v = (lval.v ^ rval.v);
		return (1);

	case T_BITNOT:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		ASSERT(np->u.expr.right == NULL);
		if (check_expr_args(&lval, NULL, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = ~ lval.v;
		return (1);

	case T_LSHIFT:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v << rval.v);
		return (1);

	case T_RSHIFT:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v >> rval.v);
		return (1);

	case T_CONDIF: {
		struct node *retnp;
		int dotrue = 0;

		/*
		 * evaluate
		 *	expression ? stmtA [ : stmtB ]
		 *
		 * first see if expression is true or false, then determine
		 * if stmtA (or stmtB, if it exists) should be evaluated.
		 *
		 * "dotrue = 1" means stmtA should be evaluated.
		 */
		if (eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval) &&
		    lval.t != UNDEFINED && lval.v != 0)
			dotrue = 1;

		ASSERT(np->u.expr.right != NULL);
		if (np->u.expr.right->t == T_CONDELSE) {
			if (dotrue)
				retnp = np->u.expr.right->u.expr.left;
			else
				retnp = np->u.expr.right->u.expr.right;
		} else {
			/* no ELSE clause */
			if (dotrue)
				retnp = np->u.expr.right;
			else {
				valuep->t = UINT64;
				valuep->v = 0;
				return (0);
			}
		}

		if (!eval_expr(retnp, ex, epnames, globals, croot,
			    arrowp, try, valuep))
			return (0);
		return (1);
	}

	case T_CONDELSE:
		/*
		 * shouldn't get here, since T_CONDELSE is supposed to be
		 * evaluated as part of T_CONDIF
		 */
		out(O_ALTFP|O_DIE, "eval_expr: wrong context for operation %s",
		    ptree_nodetype2str(np->t));
		return (0);

	case T_NE:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UNDEFINED, np))
			return (0);

		valuep->t = UINT64;
		valuep->v = (lval.v != rval.v);
		return (1);

	case T_LIST:
	case T_AND:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, valuep)) {
			/*
			 * if lhs is unknown, still check rhs. If that
			 * is false we can return false irrespectice of lhs
			 */
			if (!eval_expr(np->u.expr.right, ex, epnames, globals,
			    croot, arrowp, try, valuep))
				return (0);
			if (valuep->v != 0)
				return (0);
		}
		if (valuep->v == 0) {
			valuep->t = UINT64;
			return (1);
		}
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, valuep))
			return (0);
		valuep->t = UINT64;
		valuep->v = valuep->v == 0 ? 0 : 1;
		return (1);

	case T_OR:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, valuep)) {
			/*
			 * if lhs is unknown, still check rhs. If that
			 * is true we can return true irrespectice of lhs
			 */
			if (!eval_expr(np->u.expr.right, ex, epnames, globals,
			    croot, arrowp, try, valuep))
				return (0);
			if (valuep->v == 0)
				return (0);
		}
		if (valuep->v != 0) {
			valuep->t = UINT64;
			valuep->v = 1;
			return (1);
		}
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, valuep))
			return (0);
		valuep->t = UINT64;
		valuep->v = valuep->v == 0 ? 0 : 1;
		return (1);

	case T_NOT:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, valuep))
			return (0);
		valuep->t = UINT64;
		valuep->v = ! valuep->v;
		return (1);

	case T_ADD:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = lval.t;
		valuep->v = lval.v + rval.v;
		return (1);

	case T_SUB:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		/* since valuep is unsigned, return false if lval.v < rval.v */
		if (lval.v < rval.v) {
			out(O_ERR, "eval_expr: T_SUB result is out of range");
			valuep->t = UNDEFINED;
			return (0);
		}

		valuep->t = lval.t;
		valuep->v = lval.v - rval.v;
		return (1);

	case T_MUL:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		valuep->t = lval.t;
		valuep->v = lval.v * rval.v;
		return (1);

	case T_DIV:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		/* return false if dividing by zero */
		if (rval.v == 0) {
			out(O_ERR, "eval_expr: T_DIV division by zero");
			valuep->t = UNDEFINED;
			return (0);
		}

		valuep->t = lval.t;
		valuep->v = lval.v / rval.v;
		return (1);

	case T_MOD:
		if (!eval_expr(np->u.expr.left, ex, epnames, globals, croot,
				arrowp, try, &lval))
			return (0);
		if (!eval_expr(np->u.expr.right, ex, epnames, globals, croot,
				arrowp, try, &rval))
			return (0);
		if (check_expr_args(&lval, &rval, UINT64, np))
			return (0);

		/* return false if dividing by zero */
		if (rval.v == 0) {
			out(O_ERR, "eval_expr: T_MOD division by zero");
			valuep->t = UNDEFINED;
			return (0);
		}

		valuep->t = lval.t;
		valuep->v = lval.v % rval.v;
		return (1);

	case T_NAME:
		if (try) {
			struct iterinfo *iterinfop;

			/*
			 * at itree_create() time, we can expand simple
			 * iterators.  anything else we'll punt on.
			 */
			iterinfop = lut_lookup(ex, (void *)np->u.name.s, NULL);
			if (iterinfop != NULL) {
				/* explicit iterator; not part of pathname */
				valuep->t = UINT64;
				valuep->v = (unsigned long long)iterinfop->num;
				return (1);
			}
			return (0);
		}

		/* return address of struct node */
		valuep->t = NODEPTR;
		valuep->v = (uintptr_t)np;
		return (1);

	case T_QUOTE:
		valuep->t = STRING;
		valuep->v = (uintptr_t)np->u.quote.s;
		return (1);

	case T_FUNC:
		return (eval_func(np, ex, epnames, np->u.func.arglist,
				globals, croot, arrowp, try, valuep));

	case T_NUM:
		valuep->t = UINT64;
		valuep->v = np->u.ull;
		return (1);

	default:
		outfl(O_DIE, np->file, np->line,
		    "eval_expr: unexpected node type: %s",
		    ptree_nodetype2str(np->t));
	}
	/*NOTREACHED*/
	return (0);
}

/*
 * eval_fru() and eval_asru() don't do much, but are called from a number
 * of places.
 */
struct node *
eval_fru(struct node *np)
{
	ASSERT(np->t == T_NAME);
	return (np);
}

struct node *
eval_asru(struct node *np)
{
	ASSERT(np->t == T_NAME);
	return (np);
}