| /*	$OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $	*/ | 
 |  | 
 | /*- | 
 |  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 | 
 |  *	Thorsten Glaser <tg@mirbsd.org> | 
 |  * | 
 |  * Provided that these terms and disclaimer and all copyright notices | 
 |  * are retained or reproduced in an accompanying document, permission | 
 |  * is granted to deal in this work without restriction, including un- | 
 |  * limited rights to use, publicly perform, distribute, sell, modify, | 
 |  * merge, give away, or sublicence. | 
 |  * | 
 |  * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to | 
 |  * the utmost extent permitted by applicable law, neither express nor | 
 |  * implied; without malicious intent or gross negligence. In no event | 
 |  * may a licensor, author or contributor be held liable for indirect, | 
 |  * direct, other damage, loss, or other issues arising in any way out | 
 |  * of dealing in the work, even if advised of the possibility of such | 
 |  * damage or existence of a defect, except proven that it results out | 
 |  * of said person's immediate fault when using the work as intended. | 
 |  */ | 
 |  | 
 | #include "sh.h" | 
 |  | 
 | __RCSID("$MirOS: src/bin/mksh/expr.c,v 1.44 2010/08/14 21:35:13 tg Exp $"); | 
 |  | 
 | /* The order of these enums is constrained by the order of opinfo[] */ | 
 | enum token { | 
 | 	/* some (long) unary operators */ | 
 | 	O_PLUSPLUS = 0, O_MINUSMINUS, | 
 | 	/* binary operators */ | 
 | 	O_EQ, O_NE, | 
 | 	/* assignments are assumed to be in range O_ASN .. O_BORASN */ | 
 | 	O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN, | 
 | 	O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN, | 
 | 	O_LSHIFT, O_RSHIFT, | 
 | 	O_LE, O_GE, O_LT, O_GT, | 
 | 	O_LAND, | 
 | 	O_LOR, | 
 | 	O_TIMES, O_DIV, O_MOD, | 
 | 	O_PLUS, O_MINUS, | 
 | 	O_BAND, | 
 | 	O_BXOR, | 
 | 	O_BOR, | 
 | 	O_TERN, | 
 | 	O_COMMA, | 
 | 	/* things after this aren't used as binary operators */ | 
 | 	/* unary that are not also binaries */ | 
 | 	O_BNOT, O_LNOT, | 
 | 	/* misc */ | 
 | 	OPEN_PAREN, CLOSE_PAREN, CTERN, | 
 | 	/* things that don't appear in the opinfo[] table */ | 
 | 	VAR, LIT, END, BAD | 
 | }; | 
 | #define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA) | 
 | #define IS_ASSIGNOP(op)	((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN) | 
 |  | 
 | /* precisions; used to be enum prec but we do arithmetics on it */ | 
 | #define P_PRIMARY	0	/* VAR, LIT, (), ~ ! - + */ | 
 | #define P_MULT		1	/* * / % */ | 
 | #define P_ADD		2	/* + - */ | 
 | #define P_SHIFT		3	/* << >> */ | 
 | #define P_RELATION	4	/* < <= > >= */ | 
 | #define P_EQUALITY	5	/* == != */ | 
 | #define P_BAND		6	/* & */ | 
 | #define P_BXOR		7	/* ^ */ | 
 | #define P_BOR		8	/* | */ | 
 | #define P_LAND		9	/* && */ | 
 | #define P_LOR		10	/* || */ | 
 | #define P_TERN		11	/* ?: */ | 
 | #define P_ASSIGN	12	/* = *= /= %= += -= <<= >>= &= ^= |= */ | 
 | #define P_COMMA		13	/* , */ | 
 | #define MAX_PREC	P_COMMA | 
 |  | 
 | struct opinfo { | 
 | 	char		name[4]; | 
 | 	int		len;	/* name length */ | 
 | 	int		prec;	/* precedence: lower is higher */ | 
 | }; | 
 |  | 
 | /* Tokens in this table must be ordered so the longest are first | 
 |  * (eg, += before +). If you change something, change the order | 
 |  * of enum token too. | 
 |  */ | 
 | static const struct opinfo opinfo[] = { | 
 | 	{ "++",	 2, P_PRIMARY },	/* before + */ | 
 | 	{ "--",	 2, P_PRIMARY },	/* before - */ | 
 | 	{ "==",	 2, P_EQUALITY },	/* before = */ | 
 | 	{ "!=",	 2, P_EQUALITY },	/* before ! */ | 
 | 	{ "=",	 1, P_ASSIGN },		/* keep assigns in a block */ | 
 | 	{ "*=",	 2, P_ASSIGN }, | 
 | 	{ "/=",	 2, P_ASSIGN }, | 
 | 	{ "%=",	 2, P_ASSIGN }, | 
 | 	{ "+=",	 2, P_ASSIGN }, | 
 | 	{ "-=",	 2, P_ASSIGN }, | 
 | 	{ "<<=", 3, P_ASSIGN }, | 
 | 	{ ">>=", 3, P_ASSIGN }, | 
 | 	{ "&=",	 2, P_ASSIGN }, | 
 | 	{ "^=",	 2, P_ASSIGN }, | 
 | 	{ "|=",	 2, P_ASSIGN }, | 
 | 	{ "<<",	 2, P_SHIFT }, | 
 | 	{ ">>",	 2, P_SHIFT }, | 
 | 	{ "<=",	 2, P_RELATION }, | 
 | 	{ ">=",	 2, P_RELATION }, | 
 | 	{ "<",	 1, P_RELATION }, | 
 | 	{ ">",	 1, P_RELATION }, | 
 | 	{ "&&",	 2, P_LAND }, | 
 | 	{ "||",	 2, P_LOR }, | 
 | 	{ "*",	 1, P_MULT }, | 
 | 	{ "/",	 1, P_MULT }, | 
 | 	{ "%",	 1, P_MULT }, | 
 | 	{ "+",	 1, P_ADD }, | 
 | 	{ "-",	 1, P_ADD }, | 
 | 	{ "&",	 1, P_BAND }, | 
 | 	{ "^",	 1, P_BXOR }, | 
 | 	{ "|",	 1, P_BOR }, | 
 | 	{ "?",	 1, P_TERN }, | 
 | 	{ ",",	 1, P_COMMA }, | 
 | 	{ "~",	 1, P_PRIMARY }, | 
 | 	{ "!",	 1, P_PRIMARY }, | 
 | 	{ "(",	 1, P_PRIMARY }, | 
 | 	{ ")",	 1, P_PRIMARY }, | 
 | 	{ ":",	 1, P_PRIMARY }, | 
 | 	{ "",	 0, P_PRIMARY } | 
 | }; | 
 |  | 
 | typedef struct expr_state Expr_state; | 
 | struct expr_state { | 
 | 	const char *expression;		/* expression being evaluated */ | 
 | 	const char *tokp;		/* lexical position */ | 
 | 	struct tbl *val;		/* value from token() */ | 
 | 	struct tbl *evaling;		/* variable that is being recursively | 
 | 					 * expanded (EXPRINEVAL flag set) */ | 
 | 	int noassign;			/* don't do assigns (for ?:,&&,||) */ | 
 | 	enum token tok;			/* token from token() */ | 
 | 	bool arith;			/* evaluating an $(()) expression? */ | 
 | 	bool natural;			/* unsigned arithmetic calculation */ | 
 | }; | 
 |  | 
 | #define bivui(x, op, y)	(es->natural ?			\ | 
 | 	(mksh_ari_t)((x)->val.u op (y)->val.u) :	\ | 
 | 	(mksh_ari_t)((x)->val.i op (y)->val.i)		\ | 
 | ) | 
 | #define chvui(x, op)	do {			\ | 
 | 	if (es->natural)			\ | 
 | 		(x)->val.u = op (x)->val.u;	\ | 
 | 	else					\ | 
 | 		(x)->val.i = op (x)->val.i;	\ | 
 | } while (/* CONSTCOND */ 0) | 
 | #define stvui(x, n)	do {			\ | 
 | 	if (es->natural)			\ | 
 | 		(x)->val.u = (n);		\ | 
 | 	else					\ | 
 | 		(x)->val.i = (n);		\ | 
 | } while (/* CONSTCOND */ 0) | 
 |  | 
 | enum error_type { | 
 | 	ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE, | 
 | 	ET_LVALUE, ET_RDONLY, ET_STR | 
 | }; | 
 |  | 
 | static void evalerr(Expr_state *, enum error_type, const char *) | 
 |     MKSH_A_NORETURN; | 
 | static struct tbl *evalexpr(Expr_state *, int); | 
 | static void exprtoken(Expr_state *); | 
 | static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool); | 
 | static void assign_check(Expr_state *, enum token, struct tbl *); | 
 | static struct tbl *tempvar(void); | 
 | static struct tbl *intvar(Expr_state *, struct tbl *); | 
 |  | 
 | /* | 
 |  * parse and evaluate expression | 
 |  */ | 
 | int | 
 | evaluate(const char *expr, mksh_ari_t *rval, int error_ok, bool arith) | 
 | { | 
 | 	struct tbl v; | 
 | 	int ret; | 
 |  | 
 | 	v.flag = DEFINED|INTEGER; | 
 | 	v.type = 0; | 
 | 	ret = v_evaluate(&v, expr, error_ok, arith); | 
 | 	*rval = v.val.i; | 
 | 	return (ret); | 
 | } | 
 |  | 
 | /* | 
 |  * parse and evaluate expression, storing result in vp. | 
 |  */ | 
 | int | 
 | v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok, | 
 |     bool arith) | 
 | { | 
 | 	struct tbl *v; | 
 | 	Expr_state curstate; | 
 | 	Expr_state * const es = &curstate; | 
 | 	int i; | 
 |  | 
 | 	/* save state to allow recursive calls */ | 
 | 	curstate.expression = curstate.tokp = expr; | 
 | 	curstate.noassign = 0; | 
 | 	curstate.arith = arith; | 
 | 	curstate.evaling = NULL; | 
 | 	curstate.natural = false; | 
 |  | 
 | 	newenv(E_ERRH); | 
 | 	i = sigsetjmp(e->jbuf, 0); | 
 | 	if (i) { | 
 | 		/* Clear EXPRINEVAL in of any variables we were playing with */ | 
 | 		if (curstate.evaling) | 
 | 			curstate.evaling->flag &= ~EXPRINEVAL; | 
 | 		quitenv(NULL); | 
 | 		if (i == LAEXPR) { | 
 | 			if (error_ok == KSH_RETURN_ERROR) | 
 | 				return (0); | 
 | 			errorfz(); | 
 | 		} | 
 | 		unwind(i); | 
 | 		/* NOTREACHED */ | 
 | 	} | 
 |  | 
 | 	exprtoken(es); | 
 | 	if (es->tok == END) { | 
 | 		es->tok = LIT; | 
 | 		es->val = tempvar(); | 
 | 	} | 
 | 	v = intvar(es, evalexpr(es, MAX_PREC)); | 
 |  | 
 | 	if (es->tok != END) | 
 | 		evalerr(es, ET_UNEXPECTED, NULL); | 
 |  | 
 | 	if (es->arith && es->natural) | 
 | 		vp->flag |= INT_U; | 
 | 	if (vp->flag & INTEGER) | 
 | 		setint_v(vp, v, es->arith); | 
 | 	else | 
 | 		/* can fail if readonly */ | 
 | 		setstr(vp, str_val(v), error_ok); | 
 |  | 
 | 	quitenv(NULL); | 
 |  | 
 | 	return (1); | 
 | } | 
 |  | 
 | static void | 
 | evalerr(Expr_state *es, enum error_type type, const char *str) | 
 | { | 
 | 	char tbuf[2]; | 
 | 	const char *s; | 
 |  | 
 | 	es->arith = false; | 
 | 	switch (type) { | 
 | 	case ET_UNEXPECTED: | 
 | 		switch (es->tok) { | 
 | 		case VAR: | 
 | 			s = es->val->name; | 
 | 			break; | 
 | 		case LIT: | 
 | 			s = str_val(es->val); | 
 | 			break; | 
 | 		case END: | 
 | 			s = "end of expression"; | 
 | 			break; | 
 | 		case BAD: | 
 | 			tbuf[0] = *es->tokp; | 
 | 			tbuf[1] = '\0'; | 
 | 			s = tbuf; | 
 | 			break; | 
 | 		default: | 
 | 			s = opinfo[(int)es->tok].name; | 
 | 		} | 
 | 		warningf(true, "%s: unexpected '%s'", es->expression, s); | 
 | 		break; | 
 |  | 
 | 	case ET_BADLIT: | 
 | 		warningf(true, "%s: bad number '%s'", es->expression, str); | 
 | 		break; | 
 |  | 
 | 	case ET_RECURSIVE: | 
 | 		warningf(true, "%s: expression recurses on parameter '%s'", | 
 | 		    es->expression, str); | 
 | 		break; | 
 |  | 
 | 	case ET_LVALUE: | 
 | 		warningf(true, "%s: %s requires lvalue", | 
 | 		    es->expression, str); | 
 | 		break; | 
 |  | 
 | 	case ET_RDONLY: | 
 | 		warningf(true, "%s: %s applied to read only variable", | 
 | 		    es->expression, str); | 
 | 		break; | 
 |  | 
 | 	default: /* keep gcc happy */ | 
 | 	case ET_STR: | 
 | 		warningf(true, "%s: %s", es->expression, str); | 
 | 		break; | 
 | 	} | 
 | 	unwind(LAEXPR); | 
 | } | 
 |  | 
 | static struct tbl * | 
 | evalexpr(Expr_state *es, int prec) | 
 | { | 
 | 	struct tbl *vl, *vr = NULL, *vasn; | 
 | 	enum token op; | 
 | 	mksh_ari_t res = 0; | 
 |  | 
 | 	if (prec == P_PRIMARY) { | 
 | 		op = es->tok; | 
 | 		if (op == O_BNOT || op == O_LNOT || op == O_MINUS || | 
 | 		    op == O_PLUS) { | 
 | 			exprtoken(es); | 
 | 			vl = intvar(es, evalexpr(es, P_PRIMARY)); | 
 | 			if (op == O_BNOT) | 
 | 				chvui(vl, ~); | 
 | 			else if (op == O_LNOT) | 
 | 				chvui(vl, !); | 
 | 			else if (op == O_MINUS) | 
 | 				chvui(vl, -); | 
 | 			/* op == O_PLUS is a no-op */ | 
 | 		} else if (op == OPEN_PAREN) { | 
 | 			exprtoken(es); | 
 | 			vl = evalexpr(es, MAX_PREC); | 
 | 			if (es->tok != CLOSE_PAREN) | 
 | 				evalerr(es, ET_STR, "missing )"); | 
 | 			exprtoken(es); | 
 | 		} else if (op == O_PLUSPLUS || op == O_MINUSMINUS) { | 
 | 			exprtoken(es); | 
 | 			vl = do_ppmm(es, op, es->val, true); | 
 | 			exprtoken(es); | 
 | 		} else if (op == VAR || op == LIT) { | 
 | 			vl = es->val; | 
 | 			exprtoken(es); | 
 | 		} else { | 
 | 			evalerr(es, ET_UNEXPECTED, NULL); | 
 | 			/* NOTREACHED */ | 
 | 		} | 
 | 		if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) { | 
 | 			vl = do_ppmm(es, es->tok, vl, false); | 
 | 			exprtoken(es); | 
 | 		} | 
 | 		return (vl); | 
 | 	} | 
 | 	vl = evalexpr(es, prec - 1); | 
 | 	for (op = es->tok; IS_BINOP(op) && opinfo[(int)op].prec == prec; | 
 | 	    op = es->tok) { | 
 | 		exprtoken(es); | 
 | 		vasn = vl; | 
 | 		if (op != O_ASN) /* vl may not have a value yet */ | 
 | 			vl = intvar(es, vl); | 
 | 		if (IS_ASSIGNOP(op)) { | 
 | 			assign_check(es, op, vasn); | 
 | 			vr = intvar(es, evalexpr(es, P_ASSIGN)); | 
 | 		} else if (op != O_TERN && op != O_LAND && op != O_LOR) | 
 | 			vr = intvar(es, evalexpr(es, prec - 1)); | 
 | 		if ((op == O_DIV || op == O_MOD || op == O_DIVASN || | 
 | 		    op == O_MODASN) && vr->val.i == 0) { | 
 | 			if (es->noassign) | 
 | 				vr->val.i = 1; | 
 | 			else | 
 | 				evalerr(es, ET_STR, "zero divisor"); | 
 | 		} | 
 | 		switch ((int)op) { | 
 | 		case O_TIMES: | 
 | 		case O_TIMESASN: | 
 | 			res = bivui(vl, *, vr); | 
 | 			break; | 
 | 		case O_DIV: | 
 | 		case O_DIVASN: | 
 | 			res = bivui(vl, /, vr); | 
 | 			break; | 
 | 		case O_MOD: | 
 | 		case O_MODASN: | 
 | 			res = bivui(vl, %, vr); | 
 | 			break; | 
 | 		case O_PLUS: | 
 | 		case O_PLUSASN: | 
 | 			res = bivui(vl, +, vr); | 
 | 			break; | 
 | 		case O_MINUS: | 
 | 		case O_MINUSASN: | 
 | 			res = bivui(vl, -, vr); | 
 | 			break; | 
 | 		case O_LSHIFT: | 
 | 		case O_LSHIFTASN: | 
 | 			res = bivui(vl, <<, vr); | 
 | 			break; | 
 | 		case O_RSHIFT: | 
 | 		case O_RSHIFTASN: | 
 | 			res = bivui(vl, >>, vr); | 
 | 			break; | 
 | 		case O_LT: | 
 | 			res = bivui(vl, <, vr); | 
 | 			break; | 
 | 		case O_LE: | 
 | 			res = bivui(vl, <=, vr); | 
 | 			break; | 
 | 		case O_GT: | 
 | 			res = bivui(vl, >, vr); | 
 | 			break; | 
 | 		case O_GE: | 
 | 			res = bivui(vl, >=, vr); | 
 | 			break; | 
 | 		case O_EQ: | 
 | 			res = bivui(vl, ==, vr); | 
 | 			break; | 
 | 		case O_NE: | 
 | 			res = bivui(vl, !=, vr); | 
 | 			break; | 
 | 		case O_BAND: | 
 | 		case O_BANDASN: | 
 | 			res = bivui(vl, &, vr); | 
 | 			break; | 
 | 		case O_BXOR: | 
 | 		case O_BXORASN: | 
 | 			res = bivui(vl, ^, vr); | 
 | 			break; | 
 | 		case O_BOR: | 
 | 		case O_BORASN: | 
 | 			res = bivui(vl, |, vr); | 
 | 			break; | 
 | 		case O_LAND: | 
 | 			if (!vl->val.i) | 
 | 				es->noassign++; | 
 | 			vr = intvar(es, evalexpr(es, prec - 1)); | 
 | 			res = bivui(vl, &&, vr); | 
 | 			if (!vl->val.i) | 
 | 				es->noassign--; | 
 | 			break; | 
 | 		case O_LOR: | 
 | 			if (vl->val.i) | 
 | 				es->noassign++; | 
 | 			vr = intvar(es, evalexpr(es, prec - 1)); | 
 | 			res = bivui(vl, ||, vr); | 
 | 			if (vl->val.i) | 
 | 				es->noassign--; | 
 | 			break; | 
 | 		case O_TERN: | 
 | 			{ | 
 | 				bool ev = vl->val.i != 0; | 
 |  | 
 | 				if (!ev) | 
 | 					es->noassign++; | 
 | 				vl = evalexpr(es, MAX_PREC); | 
 | 				if (!ev) | 
 | 					es->noassign--; | 
 | 				if (es->tok != CTERN) | 
 | 					evalerr(es, ET_STR, "missing :"); | 
 | 				exprtoken(es); | 
 | 				if (ev) | 
 | 					es->noassign++; | 
 | 				vr = evalexpr(es, P_TERN); | 
 | 				if (ev) | 
 | 					es->noassign--; | 
 | 				vl = ev ? vl : vr; | 
 | 			} | 
 | 			break; | 
 | 		case O_ASN: | 
 | 			res = vr->val.i; | 
 | 			break; | 
 | 		case O_COMMA: | 
 | 			res = vr->val.i; | 
 | 			break; | 
 | 		} | 
 | 		if (IS_ASSIGNOP(op)) { | 
 | 			stvui(vr, res); | 
 | 			if (!es->noassign) { | 
 | 				if (vasn->flag & INTEGER) | 
 | 					setint_v(vasn, vr, es->arith); | 
 | 				else | 
 | 					setint(vasn, res); | 
 | 			} | 
 | 			vl = vr; | 
 | 		} else if (op != O_TERN) | 
 | 			stvui(vl, res); | 
 | 	} | 
 | 	return (vl); | 
 | } | 
 |  | 
 | static void | 
 | exprtoken(Expr_state *es) | 
 | { | 
 | 	const char *cp = es->tokp; | 
 | 	int c; | 
 | 	char *tvar; | 
 |  | 
 | 	/* skip white space */ | 
 |  skip_spaces: | 
 | 	while ((c = *cp), ksh_isspace(c)) | 
 | 		++cp; | 
 | 	if (es->tokp == es->expression && c == '#') { | 
 | 		/* expression begins with # */ | 
 | 		es->natural = true;	/* switch to unsigned */ | 
 | 		++cp; | 
 | 		goto skip_spaces; | 
 | 	} | 
 | 	es->tokp = cp; | 
 |  | 
 | 	if (c == '\0') | 
 | 		es->tok = END; | 
 | 	else if (ksh_isalphx(c)) { | 
 | 		for (; ksh_isalnux(c); c = *cp) | 
 | 			cp++; | 
 | 		if (c == '[') { | 
 | 			int len; | 
 |  | 
 | 			len = array_ref_len(cp); | 
 | 			if (len == 0) | 
 | 				evalerr(es, ET_STR, "missing ]"); | 
 | 			cp += len; | 
 | 		} else if (c == '(' /*)*/ ) { | 
 | 			/* todo: add math functions (all take single argument): | 
 | 			 * abs acos asin atan cos cosh exp int log sin sinh sqrt | 
 | 			 * tan tanh | 
 | 			 */ | 
 | 			; | 
 | 		} | 
 | 		if (es->noassign) { | 
 | 			es->val = tempvar(); | 
 | 			es->val->flag |= EXPRLVALUE; | 
 | 		} else { | 
 | 			strndupx(tvar, es->tokp, cp - es->tokp, ATEMP); | 
 | 			es->val = global(tvar); | 
 | 			afree(tvar, ATEMP); | 
 | 		} | 
 | 		es->tok = VAR; | 
 | 	} else if (c == '1' && cp[1] == '#') { | 
 | 		cp += 2; | 
 | 		cp += utf_ptradj(cp); | 
 | 		strndupx(tvar, es->tokp, cp - es->tokp, ATEMP); | 
 | 		goto process_tvar; | 
 | #ifndef MKSH_SMALL | 
 | 	} else if (c == '\'') { | 
 | 		++cp; | 
 | 		cp += utf_ptradj(cp); | 
 | 		if (*cp++ != '\'') | 
 | 			evalerr(es, ET_STR, | 
 | 			    "multi-character character constant"); | 
 | 		/* 'x' -> 1#x (x = one multibyte character) */ | 
 | 		c = cp - es->tokp; | 
 | 		tvar = alloc(c + /* NUL */ 1, ATEMP); | 
 | 		tvar[0] = '1'; | 
 | 		tvar[1] = '#'; | 
 | 		memcpy(tvar + 2, es->tokp + 1, c - 2); | 
 | 		tvar[c] = '\0'; | 
 | 		goto process_tvar; | 
 | #endif | 
 | 	} else if (ksh_isdigit(c)) { | 
 | 		while (c != '_' && (ksh_isalnux(c) || c == '#')) | 
 | 			c = *cp++; | 
 | 		strndupx(tvar, es->tokp, --cp - es->tokp, ATEMP); | 
 |  process_tvar: | 
 | 		es->val = tempvar(); | 
 | 		es->val->flag &= ~INTEGER; | 
 | 		es->val->type = 0; | 
 | 		es->val->val.s = tvar; | 
 | 		if (setint_v(es->val, es->val, es->arith) == NULL) | 
 | 			evalerr(es, ET_BADLIT, tvar); | 
 | 		afree(tvar, ATEMP); | 
 | 		es->tok = LIT; | 
 | 	} else { | 
 | 		int i, n0; | 
 |  | 
 | 		for (i = 0; (n0 = opinfo[i].name[0]); i++) | 
 | 			if (c == n0 && strncmp(cp, opinfo[i].name, | 
 | 			    (size_t)opinfo[i].len) == 0) { | 
 | 				es->tok = (enum token)i; | 
 | 				cp += opinfo[i].len; | 
 | 				break; | 
 | 			} | 
 | 		if (!n0) | 
 | 			es->tok = BAD; | 
 | 	} | 
 | 	es->tokp = cp; | 
 | } | 
 |  | 
 | /* Do a ++ or -- operation */ | 
 | static struct tbl * | 
 | do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix) | 
 | { | 
 | 	struct tbl *vl; | 
 | 	mksh_ari_t oval; | 
 |  | 
 | 	assign_check(es, op, vasn); | 
 |  | 
 | 	vl = intvar(es, vasn); | 
 | 	oval = vl->val.i; | 
 | 	if (op == O_PLUSPLUS) { | 
 | 		if (es->natural) | 
 | 			++vl->val.u; | 
 | 		else | 
 | 			++vl->val.i; | 
 | 	} else { | 
 | 		if (es->natural) | 
 | 			--vl->val.u; | 
 | 		else | 
 | 			--vl->val.i; | 
 | 	} | 
 | 	if (vasn->flag & INTEGER) | 
 | 		setint_v(vasn, vl, es->arith); | 
 | 	else | 
 | 		setint(vasn, vl->val.i); | 
 | 	if (!is_prefix)		/* undo the inc/dec */ | 
 | 		vl->val.i = oval; | 
 |  | 
 | 	return (vl); | 
 | } | 
 |  | 
 | static void | 
 | assign_check(Expr_state *es, enum token op, struct tbl *vasn) | 
 | { | 
 | 	if (es->tok == END || | 
 | 	    (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE))) | 
 | 		evalerr(es, ET_LVALUE, opinfo[(int)op].name); | 
 | 	else if (vasn->flag & RDONLY) | 
 | 		evalerr(es, ET_RDONLY, opinfo[(int)op].name); | 
 | } | 
 |  | 
 | static struct tbl * | 
 | tempvar(void) | 
 | { | 
 | 	struct tbl *vp; | 
 |  | 
 | 	vp = alloc(sizeof(struct tbl), ATEMP); | 
 | 	vp->flag = ISSET|INTEGER; | 
 | 	vp->type = 0; | 
 | 	vp->areap = ATEMP; | 
 | 	vp->ua.hval = 0; | 
 | 	vp->val.i = 0; | 
 | 	vp->name[0] = '\0'; | 
 | 	return (vp); | 
 | } | 
 |  | 
 | /* cast (string) variable to temporary integer variable */ | 
 | static struct tbl * | 
 | intvar(Expr_state *es, struct tbl *vp) | 
 | { | 
 | 	struct tbl *vq; | 
 |  | 
 | 	/* try to avoid replacing a temp var with another temp var */ | 
 | 	if (vp->name[0] == '\0' && | 
 | 	    (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER)) | 
 | 		return (vp); | 
 |  | 
 | 	vq = tempvar(); | 
 | 	if (setint_v(vq, vp, es->arith) == NULL) { | 
 | 		if (vp->flag & EXPRINEVAL) | 
 | 			evalerr(es, ET_RECURSIVE, vp->name); | 
 | 		es->evaling = vp; | 
 | 		vp->flag |= EXPRINEVAL; | 
 | 		v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith); | 
 | 		vp->flag &= ~EXPRINEVAL; | 
 | 		es->evaling = NULL; | 
 | 	} | 
 | 	return (vq); | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  * UTF-8 support code: high-level functions | 
 |  */ | 
 |  | 
 | int | 
 | utf_widthadj(const char *src, const char **dst) | 
 | { | 
 | 	size_t len; | 
 | 	unsigned int wc; | 
 | 	int width; | 
 |  | 
 | 	if (!UTFMODE || (len = utf_mbtowc(&wc, src)) == (size_t)-1 || | 
 | 	    wc == 0) | 
 | 		len = width = 1; | 
 | 	else if ((width = utf_wcwidth(wc)) < 0) | 
 | 		/* XXX use 2 for x_zotc3 here? */ | 
 | 		width = 1; | 
 |  | 
 | 	if (dst) | 
 | 		*dst = src + len; | 
 | 	return (width); | 
 | } | 
 |  | 
 | int | 
 | utf_mbswidth(const char *s) | 
 | { | 
 | 	size_t len; | 
 | 	unsigned int wc; | 
 | 	int width = 0, cw; | 
 |  | 
 | 	if (!UTFMODE) | 
 | 		return (strlen(s)); | 
 |  | 
 | 	while (*s) | 
 | 		if (((len = utf_mbtowc(&wc, s)) == (size_t)-1) || | 
 | 		    ((cw = utf_wcwidth(wc)) == -1)) { | 
 | 			s++; | 
 | 			width += 1; | 
 | 		} else { | 
 | 			s += len; | 
 | 			width += cw; | 
 | 		} | 
 | 	return (width); | 
 | } | 
 |  | 
 | const char * | 
 | utf_skipcols(const char *p, int cols) | 
 | { | 
 | 	int c = 0; | 
 |  | 
 | 	while (c < cols) { | 
 | 		if (!*p) | 
 | 			return (p + cols - c); | 
 | 		c += utf_widthadj(p, &p); | 
 | 	} | 
 | 	return (p); | 
 | } | 
 |  | 
 | size_t | 
 | utf_ptradj(const char *src) | 
 | { | 
 | 	register size_t n; | 
 |  | 
 | 	if (!UTFMODE || | 
 | 	    *(const unsigned char *)(src) < 0xC2 || | 
 | 	    (n = utf_mbtowc(NULL, src)) == (size_t)-1) | 
 | 		n = 1; | 
 | 	return (n); | 
 | } | 
 |  | 
 | /* | 
 |  * UTF-8 support code: low-level functions | 
 |  */ | 
 |  | 
 | /* CESU-8 multibyte and wide character conversion crafted for mksh */ | 
 |  | 
 | size_t | 
 | utf_mbtowc(unsigned int *dst, const char *src) | 
 | { | 
 | 	const unsigned char *s = (const unsigned char *)src; | 
 | 	unsigned int c, wc; | 
 |  | 
 | 	if ((wc = *s++) < 0x80) { | 
 |  out: | 
 | 		if (dst != NULL) | 
 | 			*dst = wc; | 
 | 		return (wc ? ((const char *)s - src) : 0); | 
 | 	} | 
 | 	if (wc < 0xC2 || wc >= 0xF0) | 
 | 		/* < 0xC0: spurious second byte */ | 
 | 		/* < 0xC2: non-minimalistic mapping error in 2-byte seqs */ | 
 | 		/* > 0xEF: beyond BMP */ | 
 | 		goto ilseq; | 
 |  | 
 | 	if (wc < 0xE0) { | 
 | 		wc = (wc & 0x1F) << 6; | 
 | 		if (((c = *s++) & 0xC0) != 0x80) | 
 | 			goto ilseq; | 
 | 		wc |= c & 0x3F; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	wc = (wc & 0x0F) << 12; | 
 |  | 
 | 	if (((c = *s++) & 0xC0) != 0x80) | 
 | 		goto ilseq; | 
 | 	wc |= (c & 0x3F) << 6; | 
 |  | 
 | 	if (((c = *s++) & 0xC0) != 0x80) | 
 | 		goto ilseq; | 
 | 	wc |= c & 0x3F; | 
 |  | 
 | 	/* Check for non-minimalistic mapping error in 3-byte seqs */ | 
 | 	if (wc >= 0x0800 && wc <= 0xFFFD) | 
 | 		goto out; | 
 |  ilseq: | 
 | 	return ((size_t)(-1)); | 
 | } | 
 |  | 
 | size_t | 
 | utf_wctomb(char *dst, unsigned int wc) | 
 | { | 
 | 	unsigned char *d; | 
 |  | 
 | 	if (wc < 0x80) { | 
 | 		*dst = wc; | 
 | 		return (1); | 
 | 	} | 
 |  | 
 | 	d = (unsigned char *)dst; | 
 | 	if (wc < 0x0800) | 
 | 		*d++ = (wc >> 6) | 0xC0; | 
 | 	else { | 
 | 		*d++ = ((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0; | 
 | 		*d++ = ((wc >> 6) & 0x3F) | 0x80; | 
 | 	} | 
 | 	*d++ = (wc & 0x3F) | 0x80; | 
 | 	return ((char *)d - dst); | 
 | } | 
 |  | 
 |  | 
 | #ifndef MKSH_mirbsd_wcwidth | 
 | /* --- begin of wcwidth.c excerpt --- */ | 
 | /*- | 
 |  * Markus Kuhn -- 2007-05-26 (Unicode 5.0) | 
 |  * | 
 |  * Permission to use, copy, modify, and distribute this software | 
 |  * for any purpose and without fee is hereby granted. The author | 
 |  * disclaims all warranties with regard to this software. | 
 |  */ | 
 |  | 
 | __RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $"); | 
 |  | 
 | int | 
 | utf_wcwidth(unsigned int c) | 
 | { | 
 | 	static const struct cbset { | 
 | 		unsigned short first; | 
 | 		unsigned short last; | 
 | 	} comb[] = { | 
 | 		{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, | 
 | 		{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, | 
 | 		{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, | 
 | 		{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, | 
 | 		{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, | 
 | 		{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, | 
 | 		{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, | 
 | 		{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, | 
 | 		{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, | 
 | 		{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, | 
 | 		{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, | 
 | 		{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, | 
 | 		{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, | 
 | 		{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, | 
 | 		{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, | 
 | 		{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, | 
 | 		{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, | 
 | 		{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, | 
 | 		{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, | 
 | 		{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, | 
 | 		{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, | 
 | 		{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, | 
 | 		{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, | 
 | 		{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, | 
 | 		{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, | 
 | 		{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, | 
 | 		{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, | 
 | 		{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, | 
 | 		{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, | 
 | 		{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, | 
 | 		{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, | 
 | 		{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, | 
 | 		{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, | 
 | 		{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, | 
 | 		{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, | 
 | 		{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, | 
 | 		{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, | 
 | 		{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, | 
 | 		{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, | 
 | 		{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, | 
 | 		{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, | 
 | 		{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, | 
 | 		{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB } | 
 | 	}; | 
 | 	size_t min = 0, mid, max = NELEM(comb) - 1; | 
 |  | 
 | 	/* test for 8-bit control characters */ | 
 | 	if (c < 32 || (c >= 0x7f && c < 0xa0)) | 
 | 		return (c ? -1 : 0); | 
 |  | 
 | 	/* binary search in table of non-spacing characters */ | 
 | 	if (c >= comb[0].first && c <= comb[max].last) | 
 | 		while (max >= min) { | 
 | 			mid = (min + max) / 2; | 
 | 			if (c > comb[mid].last) | 
 | 				min = mid + 1; | 
 | 			else if (c < comb[mid].first) | 
 | 				max = mid - 1; | 
 | 			else | 
 | 				return (0); | 
 | 		} | 
 |  | 
 | 	/* if we arrive here, c is not a combining or C0/C1 control char */ | 
 | 	return ((c >= 0x1100 && ( | 
 | 	    c <= 0x115f || /* Hangul Jamo init. consonants */ | 
 | 	    c == 0x2329 || c == 0x232a || | 
 | 	    (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */ | 
 | 	    (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */ | 
 | 	    (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */ | 
 | 	    (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */ | 
 | 	    (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */ | 
 | 	    (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */ | 
 | 	    (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1); | 
 | } | 
 | /* --- end of wcwidth.c excerpt --- */ | 
 | #endif |