1 /* expr -- evaluate expressions.
2 Copyright (C) 1986-2023 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Author: Mike Parker.
18 Modified for arbitrary-precision calculation by James Youngman.
19
20 This program evaluates expressions. Each token (operator, operand,
21 parenthesis) of the expression must be a separate argument. The
22 parser used is a reasonably general one, though any incarnation of
23 it is language-specific. It is especially nice for expressions.
24
25 No parse tree is needed; a new node is evaluated immediately.
26 One function can handle multiple operators all of equal precedence,
27 provided they all associate ((x op x) op x).
28
29 Define EVAL_TRACE to print an evaluation trace. */
30
31 #include <config.h>
32 #include <stdio.h>
33 #include <sys/types.h>
34 #include "system.h"
35
36 #include <gmp.h>
37 #include <regex.h>
38 #include "long-options.h"
39 #include "mcel.h"
40 #include "strnumcmp.h"
41 #include "xstrtol.h"
42
43 /* Various parts of this code assume size_t fits into unsigned long
44 int, the widest unsigned type that GMP supports. */
45 static_assert (SIZE_MAX <= ULONG_MAX);
46
47 /* The official name of this program (e.g., no 'g' prefix). */
48 #define PROGRAM_NAME "expr"
49
50 #define AUTHORS \
51 proper_name ("Mike Parker"), \
52 proper_name ("James Youngman"), \
53 proper_name ("Paul Eggert")
54
55 /* Exit statuses. */
56 enum
57 {
58 /* Invalid expression: e.g., its form does not conform to the
59 grammar for expressions. Our grammar is an extension of the
60 POSIX grammar. */
61 EXPR_INVALID = 2,
62
63 /* An internal error occurred, e.g., arithmetic overflow, storage
64 exhaustion. */
65 EXPR_FAILURE
66 };
67
68 /* The kinds of value we can have. */
69 enum valtype
70 {
71 integer,
72 string
73 };
74 typedef enum valtype TYPE;
75
76 /* A value is.... */
77 struct valinfo
78 {
79 TYPE type; /* Which kind. */
80 union
81 { /* The value itself. */
82 mpz_t i;
83 char *s;
84 } u;
85 };
86 typedef struct valinfo VALUE;
87
88 /* The arguments given to the program, minus the program name. */
89 static char **args;
90
91 static VALUE *eval (bool);
92 static bool nomoreargs (void);
93 static bool null (VALUE *v);
94 static void printv (VALUE *v);
95
96
97 /*
98 Find the first occurrence in the character string STRING of any character
99 in the character string ACCEPT.
100
101 Copied from gnulib's mbscspn, with two differences:
102 1. Returns 1-based position of first found character, or zero if not found.
103 2. Returned value is the logical character index, NOT byte offset.
104
105 Examples:
106 mbs_logical_cspn ('hello','a') => 0
107 mbs_logical_cspn ('hello','h') => 1
108 mbs_logical_cspn ('hello','oe') => 1
109 mbs_logical_cspn ('hello','lo') => 3
110
111 In UTF-8 \xCE\xB1 is a single character (greek alpha):
112 mbs_logical_cspn ('\xCE\xB1bc','\xCE\xB1') => 1
113 mbs_logical_cspn ('\xCE\xB1bc','c') => 3 */
114 static size_t
mbs_logical_cspn(char const * s,char const * accept)115 mbs_logical_cspn (char const *s, char const *accept)
116 {
117 size_t idx = 0;
118
119 if (accept[0] == '\0')
120 return 0;
121
122 /* General case. */
123 if (MB_CUR_MAX > 1)
124 {
125 for (char const *p = s; *p; )
126 {
127 ++idx;
128 mcel_t g = mcel_scanz (p);
129 if (g.len == 1)
130 {
131 if (mbschr (accept, *p))
132 return idx;
133 }
134 else
135 for (char const *a = accept; *a; )
136 {
137 mcel_t h = mcel_scanz (a);
138 if (mcel_cmp (g, h) == 0)
139 return idx;
140 a += h.len;
141 }
142 p += g.len;
143 }
144 }
145 else
146 {
147 /* single-byte locale,
148 convert returned byte offset to 1-based index or zero if not found. */
149 size_t i = strcspn (s, accept);
150 if (s[i])
151 return i + 1;
152 }
153
154 /* not found */
155 return 0;
156 }
157
158 /* Extract the substring of S, from logical character
159 position POS and LEN characters.
160 first character position is 1.
161 POS and LEN refer to logical characters, not octets.
162
163 Upon exit, sets v->s to the new string.
164 The new string might be empty if POS/LEN are invalid. */
165 static char *
mbs_logical_substr(char const * s,size_t pos,size_t len)166 mbs_logical_substr (char const *s, size_t pos, size_t len)
167 {
168 size_t mb_cur_max = MB_CUR_MAX;
169 idx_t llen = mb_cur_max <= 1 ? strlen (s) : mbslen (s); /* logical length */
170
171 /* characters to copy */
172 size_t vlen = MIN (len, pos <= llen ? llen - pos + 1 : 0);
173
174 char const *substart = s;
175 idx_t sublen = 0;
176 if (pos == 0 || len == SIZE_MAX)
177 {
178 /* The request is invalid. Silently yield an empty string. */
179 }
180 else if (mb_cur_max <= 1)
181 {
182 substart += pos - 1;
183 sublen = vlen;
184 }
185 else
186 for (idx_t idx = 1; *s && vlen; idx++)
187 {
188 idx_t char_bytes = mcel_scanz (s).len;
189
190 /* Skip until we reach the starting position. */
191 if (pos <= idx)
192 {
193 if (pos == idx)
194 substart = s;
195
196 /* Add one character's length in bytes. */
197 vlen--;
198 sublen += char_bytes;
199 }
200
201 s += char_bytes;
202 }
203
204 return ximemdup0 (substart, sublen);
205 }
206
207 /* Return the number of logical characters (possibly multibyte)
208 that are in string S in the first OFS octets.
209
210 Example in UTF-8:
211 "\xE2\x9D\xA7" is "U+2767 ROTATED FLORAL HEART BULLET".
212 In the string below, there are only two characters
213 up to the first 4 bytes (The U+2767 which occupies 3 bytes and 'x'):
214 mbs_count_to_offset ("\xE2\x9D\xA7xyz", 4) => 2 */
215 static size_t
mbs_offset_to_chars(char const * s,size_t ofs)216 mbs_offset_to_chars (char const *s, size_t ofs)
217 {
218 size_t c = 0;
219 for (size_t d = 0; d < ofs && s[d]; d += mcel_scanz (s + d).len)
220 c++;
221 return c;
222 }
223
224
225
226 void
usage(int status)227 usage (int status)
228 {
229 if (status != EXIT_SUCCESS)
230 emit_try_help ();
231 else
232 {
233 printf (_("\
234 Usage: %s EXPRESSION\n\
235 or: %s OPTION\n\
236 "),
237 program_name, program_name);
238 putchar ('\n');
239 fputs (HELP_OPTION_DESCRIPTION, stdout);
240 fputs (VERSION_OPTION_DESCRIPTION, stdout);
241 fputs (_("\
242 \n\
243 Print the value of EXPRESSION to standard output. A blank line below\n\
244 separates increasing precedence groups. EXPRESSION may be:\n\
245 \n\
246 ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2\n\
247 \n\
248 ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0\n\
249 "), stdout);
250 fputs (_("\
251 \n\
252 ARG1 < ARG2 ARG1 is less than ARG2\n\
253 ARG1 <= ARG2 ARG1 is less than or equal to ARG2\n\
254 ARG1 = ARG2 ARG1 is equal to ARG2\n\
255 ARG1 != ARG2 ARG1 is unequal to ARG2\n\
256 ARG1 >= ARG2 ARG1 is greater than or equal to ARG2\n\
257 ARG1 > ARG2 ARG1 is greater than ARG2\n\
258 "), stdout);
259 fputs (_("\
260 \n\
261 ARG1 + ARG2 arithmetic sum of ARG1 and ARG2\n\
262 ARG1 - ARG2 arithmetic difference of ARG1 and ARG2\n\
263 "), stdout);
264 /* Tell xgettext that the "% A" below is not a printf-style
265 format string: xgettext:no-c-format */
266 fputs (_("\
267 \n\
268 ARG1 * ARG2 arithmetic product of ARG1 and ARG2\n\
269 ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2\n\
270 ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2\n\
271 "), stdout);
272 fputs (_("\
273 \n\
274 STRING : REGEXP anchored pattern match of REGEXP in STRING\n\
275 \n\
276 match STRING REGEXP same as STRING : REGEXP\n\
277 substr STRING POS LENGTH substring of STRING, POS counted from 1\n\
278 index STRING CHARS index in STRING where any CHARS is found, or 0\n\
279 length STRING length of STRING\n\
280 "), stdout);
281 fputs (_("\
282 + TOKEN interpret TOKEN as a string, even if it is a\n\
283 keyword like 'match' or an operator like '/'\n\
284 \n\
285 ( EXPRESSION ) value of EXPRESSION\n\
286 "), stdout);
287 fputs (_("\
288 \n\
289 Beware that many operators need to be escaped or quoted for shells.\n\
290 Comparisons are arithmetic if both ARGs are numbers, else lexicographical.\n\
291 Pattern matches return the string matched between \\( and \\) or null; if\n\
292 \\( and \\) are not used, they return the number of characters matched or 0.\n\
293 "), stdout);
294 fputs (_("\
295 \n\
296 Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null\n\
297 or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.\n\
298 "), stdout);
299 emit_ancillary_info (PROGRAM_NAME);
300 }
301 exit (status);
302 }
303
304
305 int
main(int argc,char ** argv)306 main (int argc, char **argv)
307 {
308 VALUE *v;
309
310 initialize_main (&argc, &argv);
311 set_program_name (argv[0]);
312 setlocale (LC_ALL, "");
313 bindtextdomain (PACKAGE, LOCALEDIR);
314 textdomain (PACKAGE);
315
316 initialize_exit_failure (EXPR_FAILURE);
317 atexit (close_stdout);
318
319 parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, VERSION,
320 usage, AUTHORS, (char const *) nullptr);
321
322 /* The above handles --help and --version.
323 Since there is no other invocation of getopt, handle '--' here. */
324 if (1 < argc && STREQ (argv[1], "--"))
325 {
326 --argc;
327 ++argv;
328 }
329
330 if (argc <= 1)
331 {
332 error (0, 0, _("missing operand"));
333 usage (EXPR_INVALID);
334 }
335
336 args = argv + 1;
337
338 v = eval (true);
339 if (!nomoreargs ())
340 error (EXPR_INVALID, 0, _("syntax error: unexpected argument %s"),
341 quotearg_n_style (0, locale_quoting_style, *args));
342
343 printv (v);
344
345 main_exit (null (v));
346 }
347
348 /* Return a VALUE for I. */
349
350 static VALUE *
int_value(unsigned long int i)351 int_value (unsigned long int i)
352 {
353 VALUE *v = xmalloc (sizeof *v);
354 v->type = integer;
355 mpz_init_set_ui (v->u.i, i);
356 return v;
357 }
358
359 /* Return a VALUE for S. */
360
361 static VALUE *
str_value(char const * s)362 str_value (char const *s)
363 {
364 VALUE *v = xmalloc (sizeof *v);
365 v->type = string;
366 v->u.s = xstrdup (s);
367 return v;
368 }
369
370 /* Free VALUE V, including structure components. */
371
372 static void
freev(VALUE * v)373 freev (VALUE *v)
374 {
375 if (v->type == string)
376 free (v->u.s);
377 else
378 mpz_clear (v->u.i);
379 free (v);
380 }
381
382 /* Print VALUE V. */
383
384 static void
printv(VALUE * v)385 printv (VALUE *v)
386 {
387 switch (v->type)
388 {
389 case integer:
390 mpz_out_str (stdout, 10, v->u.i);
391 putchar ('\n');
392 break;
393 case string:
394 puts (v->u.s);
395 break;
396 default:
397 unreachable ();
398 }
399 }
400
401 /* Return true if V is a null-string or zero-number. */
402
403 ATTRIBUTE_PURE
404 static bool
null(VALUE * v)405 null (VALUE *v)
406 {
407 switch (v->type)
408 {
409 case integer:
410 return mpz_sgn (v->u.i) == 0;
411 case string:
412 {
413 char const *cp = v->u.s;
414 if (*cp == '\0')
415 return true;
416
417 cp += (*cp == '-');
418
419 do
420 {
421 if (*cp != '0')
422 return false;
423 }
424 while (*++cp);
425
426 return true;
427 }
428 default:
429 unreachable ();
430 }
431 }
432
433 /* Return true if CP takes the form of an integer. */
434
435 ATTRIBUTE_PURE
436 static bool
looks_like_integer(char const * cp)437 looks_like_integer (char const *cp)
438 {
439 cp += (*cp == '-');
440
441 do
442 if (! ISDIGIT (*cp))
443 return false;
444 while (*++cp);
445
446 return true;
447 }
448
449 /* Coerce V to a string value (can't fail). */
450
451 static void
tostring(VALUE * v)452 tostring (VALUE *v)
453 {
454 switch (v->type)
455 {
456 case integer:
457 {
458 char *s = mpz_get_str (nullptr, 10, v->u.i);
459 mpz_clear (v->u.i);
460 v->u.s = s;
461 v->type = string;
462 }
463 break;
464 case string:
465 break;
466 default:
467 unreachable ();
468 }
469 }
470
471 /* Coerce V to an integer value. Return true on success, false on failure. */
472
473 static bool
toarith(VALUE * v)474 toarith (VALUE *v)
475 {
476 switch (v->type)
477 {
478 case integer:
479 return true;
480 case string:
481 {
482 char *s = v->u.s;
483
484 if (! looks_like_integer (s))
485 return false;
486 if (mpz_init_set_str (v->u.i, s, 10) != 0)
487 error (EXPR_FAILURE, ERANGE, "%s", (s));
488 free (s);
489 v->type = integer;
490 return true;
491 }
492 default:
493 unreachable ();
494 }
495 }
496
497 /* Extract a size_t value from an integer value I.
498 If the value is negative, return SIZE_MAX.
499 If the value is too large, return SIZE_MAX - 1. */
500 static size_t
getsize(mpz_t i)501 getsize (mpz_t i)
502 {
503 if (mpz_sgn (i) < 0)
504 return SIZE_MAX;
505 if (mpz_fits_ulong_p (i))
506 {
507 unsigned long int ul = mpz_get_ui (i);
508 if (ul < SIZE_MAX)
509 return ul;
510 }
511 return SIZE_MAX - 1;
512 }
513
514 /* Return true and advance if the next token matches STR exactly.
515 STR must not be null. */
516
517 static bool
nextarg(char const * str)518 nextarg (char const *str)
519 {
520 if (*args == nullptr)
521 return false;
522 else
523 {
524 bool r = STREQ (*args, str);
525 args += r;
526 return r;
527 }
528 }
529
530 /* Return true if there no more tokens. */
531
532 static bool
nomoreargs(void)533 nomoreargs (void)
534 {
535 return *args == 0;
536 }
537
538 /* Report missing operand.
539 There is an implicit assumption that there was a previous argument,
540 and (args-1) is valid. */
541 static void
require_more_args(void)542 require_more_args (void)
543 {
544 if (nomoreargs ())
545 error (EXPR_INVALID, 0, _("syntax error: missing argument after %s"),
546 quotearg_n_style (0, locale_quoting_style, *(args - 1)));
547 }
548
549
550 #ifdef EVAL_TRACE
551 /* Print evaluation trace and args remaining. */
552
553 static void
trace(fxn)554 trace (fxn)
555 char *fxn;
556 {
557 char **a;
558
559 printf ("%s:", fxn);
560 for (a = args; *a; a++)
561 printf (" %s", *a);
562 putchar ('\n');
563 }
564 #endif
565
566 /* Do the : operator.
567 SV is the VALUE for the lhs (the string),
568 PV is the VALUE for the rhs (the pattern). */
569
570 static VALUE *
docolon(VALUE * sv,VALUE * pv)571 docolon (VALUE *sv, VALUE *pv)
572 {
573 VALUE *v;
574 char const *errmsg;
575 struct re_pattern_buffer re_buffer;
576 char fastmap[UCHAR_MAX + 1];
577 struct re_registers re_regs;
578 regoff_t matchlen;
579
580 tostring (sv);
581 tostring (pv);
582
583 re_regs.num_regs = 0;
584 re_regs.start = nullptr;
585 re_regs.end = nullptr;
586
587 re_buffer.buffer = nullptr;
588 re_buffer.allocated = 0;
589 re_buffer.fastmap = fastmap;
590 re_buffer.translate = nullptr;
591 re_syntax_options =
592 RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES;
593 errmsg = re_compile_pattern (pv->u.s, strlen (pv->u.s), &re_buffer);
594 if (errmsg)
595 error (EXPR_INVALID, 0, "%s", (errmsg));
596 re_buffer.newline_anchor = 0;
597
598 matchlen = re_match (&re_buffer, sv->u.s, strlen (sv->u.s), 0, &re_regs);
599 if (0 <= matchlen)
600 {
601 /* Were \(...\) used? */
602 if (re_buffer.re_nsub > 0)
603 {
604 if (re_regs.end[1] < 0)
605 v = str_value ("");
606 else
607 {
608 sv->u.s[re_regs.end[1]] = '\0';
609 v = str_value (sv->u.s + re_regs.start[1]);
610 }
611 }
612 else
613 {
614 /* In multibyte locales, convert the matched offset (=number of bytes)
615 to the number of matched characters. */
616 size_t i = (MB_CUR_MAX == 1
617 ? matchlen
618 : mbs_offset_to_chars (sv->u.s, matchlen));
619 v = int_value (i);
620 }
621 }
622 else if (matchlen == -1)
623 {
624 /* Match failed -- return the right kind of null. */
625 if (re_buffer.re_nsub > 0)
626 v = str_value ("");
627 else
628 v = int_value (0);
629 }
630 else
631 error (EXPR_FAILURE,
632 matchlen == -2 ? errno : EOVERFLOW,
633 _("error in regular expression matcher"));
634
635 if (0 < re_regs.num_regs)
636 {
637 free (re_regs.start);
638 free (re_regs.end);
639 }
640 re_buffer.fastmap = nullptr;
641 regfree (&re_buffer);
642 return v;
643 }
644
645 /* Handle bare operands and ( expr ) syntax. */
646
647 static VALUE *
eval7(bool evaluate)648 eval7 (bool evaluate)
649 {
650 VALUE *v;
651
652 #ifdef EVAL_TRACE
653 trace ("eval7");
654 #endif
655 require_more_args ();
656
657 if (nextarg ("("))
658 {
659 v = eval (evaluate);
660 if (nomoreargs ())
661 error (EXPR_INVALID, 0, _("syntax error: expecting ')' after %s"),
662 quotearg_n_style (0, locale_quoting_style, *(args - 1)));
663 if (!nextarg (")"))
664 error (EXPR_INVALID, 0, _("syntax error: expecting ')' instead of %s"),
665 quotearg_n_style (0, locale_quoting_style, *args));
666 return v;
667 }
668
669 if (nextarg (")"))
670 error (EXPR_INVALID, 0, _("syntax error: unexpected ')'"));
671
672 return str_value (*args++);
673 }
674
675 /* Handle match, substr, index, and length keywords, and quoting "+". */
676
677 static VALUE *
eval6(bool evaluate)678 eval6 (bool evaluate)
679 {
680 VALUE *l;
681 VALUE *r;
682 VALUE *v;
683 VALUE *i1;
684 VALUE *i2;
685
686 #ifdef EVAL_TRACE
687 trace ("eval6");
688 #endif
689 if (nextarg ("+"))
690 {
691 require_more_args ();
692 return str_value (*args++);
693 }
694 else if (nextarg ("length"))
695 {
696 r = eval6 (evaluate);
697 tostring (r);
698 v = int_value (mbslen (r->u.s));
699 freev (r);
700 return v;
701 }
702 else if (nextarg ("match"))
703 {
704 l = eval6 (evaluate);
705 r = eval6 (evaluate);
706 if (evaluate)
707 {
708 v = docolon (l, r);
709 freev (l);
710 }
711 else
712 v = l;
713 freev (r);
714 return v;
715 }
716 else if (nextarg ("index"))
717 {
718 size_t pos;
719
720 l = eval6 (evaluate);
721 r = eval6 (evaluate);
722 tostring (l);
723 tostring (r);
724 pos = mbs_logical_cspn (l->u.s, r->u.s);
725 v = int_value (pos);
726 freev (l);
727 freev (r);
728 return v;
729 }
730 else if (nextarg ("substr"))
731 {
732 l = eval6 (evaluate);
733 i1 = eval6 (evaluate);
734 i2 = eval6 (evaluate);
735 tostring (l);
736
737 if (!toarith (i1) || !toarith (i2))
738 v = str_value ("");
739 else
740 {
741 size_t pos = getsize (i1->u.i);
742 size_t len = getsize (i2->u.i);
743
744 char *s = mbs_logical_substr (l->u.s, pos, len);
745 v = str_value (s);
746 free (s);
747 }
748 freev (l);
749 freev (i1);
750 freev (i2);
751 return v;
752 }
753 else
754 return eval7 (evaluate);
755 }
756
757 /* Handle : operator (pattern matching).
758 Calls docolon to do the real work. */
759
760 static VALUE *
eval5(bool evaluate)761 eval5 (bool evaluate)
762 {
763 VALUE *l;
764 VALUE *r;
765 VALUE *v;
766
767 #ifdef EVAL_TRACE
768 trace ("eval5");
769 #endif
770 l = eval6 (evaluate);
771 while (true)
772 {
773 if (nextarg (":"))
774 {
775 r = eval6 (evaluate);
776 if (evaluate)
777 {
778 v = docolon (l, r);
779 freev (l);
780 l = v;
781 }
782 freev (r);
783 }
784 else
785 return l;
786 }
787 }
788
789 /* Handle *, /, % operators. */
790
791 static VALUE *
eval4(bool evaluate)792 eval4 (bool evaluate)
793 {
794 VALUE *l;
795 VALUE *r;
796 enum { multiply, divide, mod } fxn;
797
798 #ifdef EVAL_TRACE
799 trace ("eval4");
800 #endif
801 l = eval5 (evaluate);
802 while (true)
803 {
804 if (nextarg ("*"))
805 fxn = multiply;
806 else if (nextarg ("/"))
807 fxn = divide;
808 else if (nextarg ("%"))
809 fxn = mod;
810 else
811 return l;
812 r = eval5 (evaluate);
813 if (evaluate)
814 {
815 if (!toarith (l) || !toarith (r))
816 error (EXPR_INVALID, 0, _("non-integer argument"));
817 if (fxn != multiply && mpz_sgn (r->u.i) == 0)
818 error (EXPR_INVALID, 0, _("division by zero"));
819 ((fxn == multiply ? mpz_mul
820 : fxn == divide ? mpz_tdiv_q
821 : mpz_tdiv_r)
822 (l->u.i, l->u.i, r->u.i));
823 }
824 freev (r);
825 }
826 }
827
828 /* Handle +, - operators. */
829
830 static VALUE *
eval3(bool evaluate)831 eval3 (bool evaluate)
832 {
833 VALUE *l;
834 VALUE *r;
835 enum { plus, minus } fxn;
836
837 #ifdef EVAL_TRACE
838 trace ("eval3");
839 #endif
840 l = eval4 (evaluate);
841 while (true)
842 {
843 if (nextarg ("+"))
844 fxn = plus;
845 else if (nextarg ("-"))
846 fxn = minus;
847 else
848 return l;
849 r = eval4 (evaluate);
850 if (evaluate)
851 {
852 if (!toarith (l) || !toarith (r))
853 error (EXPR_INVALID, 0, _("non-integer argument"));
854 (fxn == plus ? mpz_add : mpz_sub) (l->u.i, l->u.i, r->u.i);
855 }
856 freev (r);
857 }
858 }
859
860 /* Handle comparisons. */
861
862 static VALUE *
eval2(bool evaluate)863 eval2 (bool evaluate)
864 {
865 VALUE *l;
866
867 #ifdef EVAL_TRACE
868 trace ("eval2");
869 #endif
870 l = eval3 (evaluate);
871 while (true)
872 {
873 VALUE *r;
874 enum
875 {
876 less_than, less_equal, equal, not_equal, greater_equal, greater_than
877 } fxn;
878 bool val = false;
879
880 if (nextarg ("<"))
881 fxn = less_than;
882 else if (nextarg ("<="))
883 fxn = less_equal;
884 else if (nextarg ("=") || nextarg ("=="))
885 fxn = equal;
886 else if (nextarg ("!="))
887 fxn = not_equal;
888 else if (nextarg (">="))
889 fxn = greater_equal;
890 else if (nextarg (">"))
891 fxn = greater_than;
892 else
893 return l;
894 r = eval3 (evaluate);
895
896 if (evaluate)
897 {
898 int cmp;
899 tostring (l);
900 tostring (r);
901
902 if (looks_like_integer (l->u.s) && looks_like_integer (r->u.s))
903 cmp = strintcmp (l->u.s, r->u.s);
904 else
905 {
906 errno = 0;
907 cmp = strcoll (l->u.s, r->u.s);
908
909 if (errno)
910 {
911 error (0, errno, _("string comparison failed"));
912 error (0, 0, _("set LC_ALL='C' to work around the problem"));
913 error (EXPR_INVALID, 0,
914 _("the strings compared were %s and %s"),
915 quotearg_n_style (0, locale_quoting_style, l->u.s),
916 quotearg_n_style (1, locale_quoting_style, r->u.s));
917 }
918 }
919
920 switch (fxn)
921 {
922 case less_than: val = (cmp < 0); break;
923 case less_equal: val = (cmp <= 0); break;
924 case equal: val = (cmp == 0); break;
925 case not_equal: val = (cmp != 0); break;
926 case greater_equal: val = (cmp >= 0); break;
927 case greater_than: val = (cmp > 0); break;
928 default: unreachable ();
929 }
930 }
931
932 freev (l);
933 freev (r);
934 l = int_value (val);
935 }
936 }
937
938 /* Handle &. */
939
940 static VALUE *
eval1(bool evaluate)941 eval1 (bool evaluate)
942 {
943 VALUE *l;
944 VALUE *r;
945
946 #ifdef EVAL_TRACE
947 trace ("eval1");
948 #endif
949 l = eval2 (evaluate);
950 while (true)
951 {
952 if (nextarg ("&"))
953 {
954 r = eval2 (evaluate && !null (l));
955 if (null (l) || null (r))
956 {
957 freev (l);
958 freev (r);
959 l = int_value (0);
960 }
961 else
962 freev (r);
963 }
964 else
965 return l;
966 }
967 }
968
969 /* Handle |. */
970
971 static VALUE *
eval(bool evaluate)972 eval (bool evaluate)
973 {
974 VALUE *l;
975 VALUE *r;
976
977 #ifdef EVAL_TRACE
978 trace ("eval");
979 #endif
980 l = eval1 (evaluate);
981 while (true)
982 {
983 if (nextarg ("|"))
984 {
985 r = eval1 (evaluate && null (l));
986 if (null (l))
987 {
988 freev (l);
989 l = r;
990 if (null (l))
991 {
992 freev (l);
993 l = int_value (0);
994 }
995 }
996 else
997 freev (r);
998 }
999 else
1000 return l;
1001 }
1002 }
1003