xref: /titanic_51/usr/src/lib/libcmd/common/expr.c (revision a799b1e741b6f59012a469e6b57c40cb8061127b)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2008 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                  David Korn <dgk@research.att.com>                   *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 
23 /*
24  * expr.c
25  * Written by David Korn
26  * Tue Oct 31 08:48:11 EST 1995
27  */
28 
29 static const char usage[] =
30 "[-?\n@(#)$Id: expr (AT&T Research) 2008-01-30 $\n]"
31 USAGE_LICENSE
32 "[+NAME?expr - evaluate arguments as an expression]"
33 "[+DESCRIPTION?\bexpr\b evaluates an expression given as arguments and writes "
34 	"the result to standard output.  The character \b0\b will be written "
35 	"to indicate a zero value and nothing will be written to indicate an "
36 	"empty string.]"
37 "[+?Most of the functionality of \bexpr\b is provided in a more natural "
38 	"way by the shell, \bsh\b(1), and \bexpr\b is provided primarily "
39 	"for backward compatibility.]"
40 "[+?Terms of the expression must be separate arguments.  A string argument is "
41 	"one that can not be identified as an integer.  Integer-valued "
42 	"arguments may be preceded by a unary plus or minus sign.  Because "
43 	"many of the operators use characters that have special meaning to "
44 	"the shell, they must be quoted when entered from the shell.]"
45 
46 "[+?Expressions are formed from the operators listed below in order "
47 	"of increasing precedence within groups.  All of the operators are "
48 	"left associative. The symbols \aexpr1\a and \aexpr2\a represent "
49 	"expressions formed from strings and integers and the following "
50 	"operators:]{"
51 	"[+\aexpr1\a \b|\b \aexpr2\a?Returns the evaluation of \aexpr1\a if "
52 	"it is neither null nor 0, otherwise returns the evaluation of expr2.]"
53 
54 	"[+\aexpr1\a \b&\b \aexpr2\a?Returns the evaluation of \aexpr1\a if "
55 	"neither expression evaluates to null or 0, otherwise returns 0.]"
56 
57 	"[+\aexpr1\a \aop\a \aexpr2\a?Returns the result of a decimal integer "
58 	"comparison if both arguments are integers; otherwise, returns the "
59 	"result of a string comparison using the locale-specific collation "
60 	"sequence. The result of each comparison will be 1 if the specified "
61 	"relationship is true, or 0 if the relationship is false.  \aop\a "
62 	"can be one of the following:]{"
63 		"[+=?Equal.]"
64 		"[+==?Equal.]"
65 		"[+>?Greater than.]"
66 		"[+>=?Greater than or equal to.]"
67 		"[+<?Less than.]"
68 		"[+<=?Less than or equal to.]"
69 		"[+!=?Not equal to.]"
70 		"}"
71 
72 	"[+\aexpr1\a \aop\a \aexpr2\a?Where \aop\a is \b+\b or \b-\b; "
73 		"addition or subtraction of decimal integer-valued arguments.]"
74 	"[+\aexpr1\a \aop\a \aexpr2\a?Where \aop\a is \b*\b, \b/\b or \b%\b; "
75 		"multiplication, division, or remainder of the	decimal	"
76 		"integer-valued arguments.]"
77 	"[+\aexpr1\a \b::\b \aexpr2\a?The matching operator : compares "
78 		"\aexpr1\a with \aexpr2\a, which must be a BRE.  Normally, "
79 		"the matching operator returns the number of bytes matched "
80 		"and 0 on failure.  However, if the pattern contains at "
81 		"least one sub-expression [\\( . . .\\)]], the string "
82 		"corresponding to \\1 will be returned.]"
83 	"[+( \aexpr1\a )?Grouping symbols.  An expression can "
84 		"be placed within parenthesis to change precedence.]"
85 	"[+match\b \astring\a \aexpr\a?Equivalent to \astring\a \b:\b "
86 		"\aexpr\a.]"
87 	"[+substr\b \astring\a \apos\a \alength\a?\alength\a character "
88 		"substring of \astring\a starting at \apos\a "
89 		"(counting from 1).]"
90 	"[+index\b \astring\a \achars\a?The position in \astring\a "
91 		"(counting from 1) of the leftmost occurrence of any "
92 		"character in \achars\a.]"
93 	"[+length\b \astring\a?The number of characters in \astring\a.]"
94 	"[+quote\b \atoken\a?Treat \atoken\a as a string operand.]"
95 	"}"
96 "[+?For backwards compatibility, unrecognized options beginning with "
97 	"a \b-\b will be treated as operands.  Portable applications "
98 	"should use \b--\b to indicate end of options.]"
99 
100 "\n"
101 "\n operand ...\n"
102 "\n"
103 
104 "[+EXIT STATUS?]{"
105 	"[+0?The expression is neither null nor	0.]"
106 	"[+1?The expression is null or 0.]"
107 	"[+2?Invalid expressions.]"
108 	"[+>2?An error occurred.]"
109 	"}"
110 "[+SEE ALSO?\bregcomp\b(5), \bgrep\b(1), \bsh\b(1)]"
111 ;
112 
113 #include	<cmd.h>
114 #include	<regex.h>
115 
116 #define T_ADD	0x100
117 #define T_MULT	0x200
118 #define T_CMP	0x400
119 #define T_FUN	0x800
120 #define T_OP	7
121 #define T_NUM	1
122 #define T_STR	2
123 
124 #define OP_EQ		(T_CMP|0)
125 #define OP_GT		(T_CMP|1)
126 #define OP_LT		(T_CMP|2)
127 #define OP_GE		(T_CMP|3)
128 #define OP_LE		(T_CMP|4)
129 #define OP_NE		(T_CMP|5)
130 #define OP_PLUS		(T_ADD|0)
131 #define OP_MINUS	(T_ADD|1)
132 #define OP_MULT		(T_MULT|0)
133 #define OP_DIV		(T_MULT|1)
134 #define OP_MOD		(T_MULT|2)
135 #define OP_INDEX	(T_FUN|0)
136 #define OP_LENGTH	(T_FUN|1)
137 #define OP_MATCH	(T_FUN|2)
138 #define OP_QUOTE	(T_FUN|3)
139 #define OP_SUBSTR	(T_FUN|4)
140 
141 #define numeric(np)	((np)->type&T_NUM)
142 
143 static const struct Optable_s
144 {
145 	const char	opname[3];
146 	int		op;
147 }
148 optable[] =
149 {
150 	"|",	'|',
151 	"&",	'&',
152 	"=",	OP_EQ,
153 	"==",	OP_EQ,
154 	">",	OP_GT,
155 	"<",	OP_LT,
156 	">=",	OP_GE,
157 	"<=",	OP_LE,
158 	"!=",	OP_NE,
159 	"+",	OP_PLUS,
160 	"-",	OP_MINUS,
161 	"*",	OP_MULT,
162 	"/",	OP_DIV,
163 	"%",	OP_MOD,
164 	":",	':',
165 	"(",	'(',
166 	")",	')'
167 };
168 
169 typedef struct Node_s
170 {
171 	int	type;
172 	long	num;
173 	char	*str;
174 } Node_t;
175 
176 typedef struct State_s
177 {
178 	int	standard;
179 	char**	arglist;
180 	char	buf[36];
181 } State_t;
182 
183 static int expr_or(State_t*, Node_t*);
184 
185 static int getnode(State_t* state, Node_t *np)
186 {
187 	register char*	sp;
188 	register char*	cp;
189 	register int	i;
190 	register int	j;
191 	register int	k;
192 	register int	tok;
193 	char*		ep;
194 
195 	if (!(cp = *state->arglist++))
196 		error(ERROR_exit(2), "argument expected");
197 	if (!state->standard)
198 		switch (cp[0])
199 		{
200 		case 'i':
201 			if (cp[1] == 'n' && !strcmp(cp, "index"))
202 			{
203 				if (!(cp = *state->arglist++))
204 					error(ERROR_exit(2), "string argument expected");
205 				if (!(ep = *state->arglist++))
206 					error(ERROR_exit(2), "chars argument expected");
207 				np->num = (ep = strpbrk(cp, ep)) ? (ep - cp + 1) : 0;
208 				np->type = T_NUM;
209 				goto next;
210 			}
211 			break;
212 		case 'l':
213 			if (cp[1] == 'e' && !strcmp(cp, "length"))
214 			{
215 				if (!(cp = *state->arglist++))
216 					error(ERROR_exit(2), "string argument expected");
217 				np->num = strlen(cp);
218 				np->type = T_NUM;
219 				goto next;
220 			}
221 			break;
222 		case 'm':
223 			if (cp[1] == 'a' && !strcmp(cp, "match"))
224 			{
225 				if (!(np->str = *state->arglist++))
226 					error(ERROR_exit(2), "pattern argument expected");
227 				np->type = T_STR;
228 				return ':';
229 			}
230 			break;
231 		case 'q':
232 			if (cp[1] == 'u' && !strcmp(cp, "quote") && !(cp = *state->arglist++))
233 				error(ERROR_exit(2), "string argument expected");
234 			break;
235 		case 's':
236 			if (cp[1] == 'u' && !strcmp(cp, "substr"))
237 			{
238 				if (!(sp = *state->arglist++))
239 					error(ERROR_exit(2), "string argument expected");
240 				if (!(cp = *state->arglist++))
241 					error(ERROR_exit(2), "position argument expected");
242 				i = strtol(cp, &ep, 10);
243 				if (*ep || --i < 0)
244 					i = -1;
245 				if (!(cp = *state->arglist++))
246 					error(ERROR_exit(2), "length argument expected");
247 				j = strtol(cp, &ep, 10);
248 				if (*ep)
249 					j = -1;
250 				k = strlen(sp);
251 				if (i < 0 || i >= k || j < 0)
252 					sp = "";
253 				else
254 				{
255 					sp += i;
256 					k -= i;
257 					if (j < k)
258 						sp[j] = 0;
259 				}
260 				np->type = T_STR;
261 				np->str = sp;
262 				goto next;
263 			}
264 			break;
265 		}
266 	if (*cp=='(' && cp[1]==0)
267 	{
268 		tok = expr_or(state, np);
269 		if (tok != ')')
270 			error(ERROR_exit(2),"closing parenthesis missing");
271 	}
272 	else
273 	{
274 		np->type = T_STR;
275 		np->str = cp;
276 		if (*cp)
277 		{
278 			np->num = strtol(np->str,&ep,10);
279 			if (!*ep)
280 				np->type |= T_NUM;
281 		}
282 	}
283  next:
284 	if (!(cp = *state->arglist))
285 		return 0;
286 	state->arglist++;
287 	for (i=0; i < sizeof(optable)/sizeof(*optable); i++)
288 		if (*cp==optable[i].opname[0] && cp[1]==optable[i].opname[1])
289 			return optable[i].op;
290 	error(ERROR_exit(2),"%s: unknown operator argument",cp);
291 	return 0;
292 }
293 
294 static int expr_cond(State_t* state, Node_t *np)
295 {
296 	register int	tok = getnode(state, np);
297 
298 	while (tok==':')
299 	{
300 		regex_t re;
301 		regmatch_t match[2];
302 		int n;
303 		Node_t rp;
304 		char *cp;
305 		tok = getnode(state, &rp);
306 		if (np->type&T_STR)
307 			cp = np->str;
308 		else
309 			sfsprintf(cp=state->buf,sizeof(state->buf),"%d",np->num);
310 		np->num = 0;
311 		np->type = T_NUM;
312 		if (n = regcomp(&re, rp.str, REG_LEFT|REG_LENIENT))
313 			regfatal(&re, ERROR_exit(2), n);
314 		if (!(n = regexec(&re, cp, elementsof(match), match, 0)))
315 		{
316 			if (re.re_nsub > 0)
317 			{
318 				np->type = T_STR;
319 				if (match[1].rm_so >= 0)
320 				{
321 					np->str = cp + match[1].rm_so;
322 					np->str[match[1].rm_eo - match[1].rm_so] = 0;
323 					np->num = strtol(np->str,&cp,10);
324 					if (cp!=np->str && *cp==0)
325 						np->type |= T_NUM;
326 				}
327 				else
328 					np->str = "";
329 			}
330 			else
331 				np->num = match[0].rm_eo - match[0].rm_so;
332 		}
333 		else if (n != REG_NOMATCH)
334 			regfatal(&re, ERROR_exit(2), n);
335 		else if (re.re_nsub)
336 		{
337 			np->str = "";
338 			np->type = T_STR;
339 		}
340 		regfree(&re);
341 	}
342 	return tok;
343 }
344 
345 static int expr_mult(State_t* state, Node_t *np)
346 {
347 	register int	tok = expr_cond(state, np);
348 
349 	while ((tok&~T_OP)==T_MULT)
350 	{
351 		Node_t rp;
352 		int op = (tok&T_OP);
353 		tok = expr_cond(state, &rp);
354 		if (!numeric(np) || !numeric(&rp))
355 			error(ERROR_exit(2),"non-numeric argument");
356 		if (op && rp.num==0)
357 			error(ERROR_exit(2),"division by zero");
358 		switch(op)
359 		{
360 		    case 0:
361 			np->num *= rp.num;
362 			break;
363 		    case 1:
364 			np->num /= rp.num;
365 			break;
366 		    case 2:
367 			np->num %= rp.num;
368 		}
369 		np->type = T_NUM;
370 	}
371 	return tok;
372 }
373 
374 static int expr_add(State_t* state, Node_t *np)
375 {
376 	register int	tok = expr_mult(state, np);
377 
378 	while ((tok&~T_OP)==T_ADD)
379 	{
380 		Node_t rp;
381 		int op = (tok&T_OP);
382 		tok = expr_mult(state, &rp);
383 		if (!numeric(np) || !numeric(&rp))
384 			error(ERROR_exit(2),"non-numeric argument");
385 		if (op)
386 			np->num -= rp.num;
387 		else
388 			np->num += rp.num;
389 		np->type = T_NUM;
390 	}
391 	return tok;
392 }
393 
394 static int expr_cmp(State_t* state, Node_t *np)
395 {
396 	register int	tok = expr_add(state, np);
397 
398 	while ((tok&~T_OP)==T_CMP)
399 	{
400 		Node_t rp;
401 		register char *left,*right;
402 		char buff1[36],buff2[36];
403 		int op = (tok&T_OP);
404 		tok = expr_add(state, &rp);
405 		if (numeric(&rp) && numeric(np))
406 			op |= 010;
407 		else
408 		{
409 			if (np->type&T_STR)
410 				left = np->str;
411 			else
412 				sfsprintf(left=buff1,sizeof(buff1),"%d",np->num);
413 			if (rp.type&T_STR)
414 				right = rp.str;
415 			else
416 				sfsprintf(right=buff2,sizeof(buff2),"%d",rp.num);
417 		}
418 		switch(op)
419 		{
420 		    case 0:
421 			np->num = streq(left,right);
422 			break;
423 		    case 1:
424 			np->num = (strcoll(left,right)>0);
425 			break;
426 		    case 2:
427 			np->num = (strcoll(left,right)<0);
428 			break;
429 		    case 3:
430 			np->num = (strcoll(left,right)>=0);
431 			break;
432 		    case 4:
433 			np->num = (strcoll(left,right)<=0);
434 			break;
435 		    case 5:
436 			np->num = !streq(left,right);
437 			break;
438 		    case 010:
439 			np->num = (np->num==rp.num);
440 			break;
441 		    case 011:
442 			np->num = (np->num>rp.num);
443 			break;
444 		    case 012:
445 			np->num = (np->num<rp.num);
446 			break;
447 		    case 013:
448 			np->num = (np->num>=rp.num);
449 			break;
450 		    case 014:
451 			np->num = (np->num<=rp.num);
452 			break;
453 		    case 015:
454 			np->num = (np->num!=rp.num);
455 			break;
456 		}
457 		np->type = T_NUM;
458 	}
459 	return tok;
460 }
461 
462 static int expr_and(State_t* state, Node_t *np)
463 {
464 	register int	tok = expr_cmp(state, np);
465 	while (tok=='&')
466 	{
467 		Node_t rp;
468 		tok = expr_cmp(state, &rp);
469 		if ((numeric(&rp) && rp.num==0) || *rp.str==0)
470 		{
471 			np->num = 0;
472 			np->type=T_NUM;
473 		}
474 	}
475 	return tok;
476 }
477 
478 static int expr_or(State_t* state, Node_t *np)
479 {
480 	register int	tok = expr_and(state, np);
481 	while (tok=='|')
482 	{
483 		Node_t rp;
484 		tok = expr_and(state, &rp);
485 		if ((numeric(np) && np->num==0) || *np->str==0)
486 			*np = rp;
487 	}
488 	return tok;
489 }
490 
491 int
492 b_expr(int argc, char *argv[], void *context)
493 {
494 	State_t	state;
495 	Node_t	node;
496 	int	n;
497 
498 	cmdinit(argc, argv,context, ERROR_CATALOG, 0);
499 	state.standard = !strcmp(astconf("CONFORMANCE", NiL, NiL), "standard");
500 #if 0
501 	if (state.standard)
502 		state.arglist = argv+1;
503 	else
504 #endif
505 	{
506 		while (n=optget(argv, usage))
507 		{
508 			/*
509 			 * NOTE: this loop ignores all but literal -- and -?
510 			 *	 out of kindness for obsolescent usage
511 			 *	 (and is ok with the standard) but strict
512 			 *	 getopt conformance would give usage for all
513 			 *	 unknown - options
514 			 */
515 			if(n=='?')
516 				error(ERROR_usage(2), "%s", opt_info.arg);
517 			if (opt_info.option[1] != '?')
518 				break;
519 			error(ERROR_usage(2), "%s", opt_info.arg);
520 		}
521 		if (error_info.errors)
522 			error(ERROR_usage(2),"%s",optusage((char*)0));
523 		state.arglist = argv+opt_info.index;
524 	}
525 	if (expr_or(&state, &node))
526 		error(ERROR_exit(2),"syntax error");
527 	if (node.type&T_STR)
528 	{
529 		if (*node.str)
530 			sfprintf(sfstdout,"%s\n",node.str);
531 	}
532 	else
533 		sfprintf(sfstdout,"%d\n",node.num);
534 	return numeric(&node)?node.num==0:*node.str==0;
535 }
536