1/* See LICENSE file for copyright and license details. */
2
3#include <limits.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include "utf.h"
9#include "util.h"
10
11/* tokens, one-character operators represent themselves */
12enum { VAL = CHAR_MAX + 1, GE, LE, NE };
13
14struct val {
15 char *str;
16 long long num;
17};
18
19static void
20tonum(struct val *v)
21{
22 const char *errstr;
23 long long d;
24
25 /* check if val is the result of an earlier calculation */
26 if (!v->str)
27 return;
28
29 d = strtonum(v->str, LLONG_MIN, LLONG_MAX, &errstr);
30 if (errstr)
31 enprintf(2, "error: expected integer, got %s\n", v->str);
32 v->num = d;
33}
34
35static void
36ezero(struct val *v)
37{
38 if (v->num != 0)
39 return;
40 enprintf(2, "division by zero\n");
41}
42
43static int
44valcmp(struct val *a, struct val *b)
45{
46 int ret;
47 const char *err1, *err2;
48 long long d1, d2;
49
50 d1 = strtonum(a->str, LLONG_MIN, LLONG_MAX, &err1);
51 d2 = strtonum(b->str, LLONG_MIN, LLONG_MAX, &err2);
52
53 if (!err1 && !err2) {
54 ret = (d1 > d2) - (d1 < d2);
55 } else {
56 ret = strcmp(a->str, b->str);
57 }
58
59 return ret;
60}
61
62static void
63match(struct val *vstr, struct val *vregx, struct val *ret)
64{
65 regex_t re;
66 regmatch_t matches[2];
67 size_t anchlen;
68 char *s, *p, *anchreg;
69 char *str = vstr->str, *regx = vregx->str;
70
71 /* anchored regex */
72 anchlen = strlen(regx) + 1 + 1;
73 anchreg = emalloc(anchlen);
74 estrlcpy(anchreg, "^", anchlen);
75 estrlcat(anchreg, regx, anchlen);
76 enregcomp(3, &re, anchreg, 0);
77 free(anchreg);
78
79 if (regexec(&re, str, 2, matches, 0)) {
80 regfree(&re);
81 ret->str = re.re_nsub ? "" : NULL;
82 return;
83 } else if (re.re_nsub) {
84 regfree(&re);
85
86 s = str + matches[1].rm_so;
87 p = str + matches[1].rm_eo;
88 *p = '\0';
89 ret->str = enstrdup(3, s);
90 return;
91 } else {
92 regfree(&re);
93 str += matches[0].rm_so;
94 ret->num = utfnlen(str, matches[0].rm_eo - matches[0].rm_so);
95 return;
96 }
97}
98
99static void
100doop(int *ophead, int *opp, struct val *valhead, struct val *valp)
101{
102 struct val ret = {.str = NULL, .num = 0}, *a, *b;
103 int op;
104
105 (void)ophead;
106
107 /* an operation "a op b" needs an operator and two values */
108 if (opp[-1] == '(')
109 enprintf(2, "syntax error: extra (\n");
110 if (valp - valhead < 2)
111 enprintf(2, "syntax error: missing expression or extra operator\n");
112
113 a = valp - 2;
114 b = valp - 1;
115 op = opp[-1];
116
117 switch (op) {
118 case '|':
119 if (a->str && *a->str)
120 ret.str = a->str;
121 else if (!a->str && a->num)
122 ret.num = a->num;
123 else if (b->str && *b->str)
124 ret.str = b->str;
125 else
126 ret.num = b->num;
127 break;
128 case '&':
129 if (((a->str && *a->str) || a->num) && ((b->str && *b->str) || b->num)) {
130 ret.str = a->str;
131 ret.num = a->num;
132 }
133 break;
134
135 case '=':
136 ret.num = (valcmp(a, b) == 0);
137 break;
138 case '>':
139 ret.num = (valcmp(a, b) > 0);
140 break;
141 case GE:
142 ret.num = (valcmp(a, b) >= 0);
143 break;
144 case '<':
145 ret.num = (valcmp(a, b) < 0);
146 break;
147 case LE:
148 ret.num = (valcmp(a, b) <= 0);
149 break;
150 case NE:
151 ret.num = (valcmp(a, b) != 0);
152 break;
153
154 case '+':
155 tonum(a);
156 tonum(b);
157 ret.num = a->num + b->num;
158 break;
159 case '-':
160 tonum(a);
161 tonum(b);
162 ret.num = a->num - b->num;
163 break;
164 case '*':
165 tonum(a);
166 tonum(b);
167 ret.num = a->num * b->num;
168 break;
169 case '/':
170 tonum(a);
171 tonum(b);
172 ezero(b);
173 ret.num = a->num / b->num;
174 break;
175 case '%':
176 tonum(a);
177 tonum(b);
178 ezero(b);
179 ret.num = a->num % b->num;
180 break;
181
182 case ':':
183 match(a, b, &ret);
184 break;
185 }
186
187 valp[-2] = ret;
188}
189
190static int
191lex(char *s, struct val *v)
192{
193 int type = VAL;
194 char *ops = "|&=><+-*/%():";
195
196 (void)v;
197
198 if (s[0] && strchr(ops, s[0]) && !s[1]) {
199 /* one-char operand */
200 type = s[0];
201 } else if (s[0] && strchr("><!", s[0]) && s[1] == '=' && !s[2]) {
202 /* two-char operand */
203 type = (s[0] == '>') ? GE : (s[0] == '<') ? LE : NE;
204 }
205
206 return type;
207}
208
209static int
210parse(char *expr[], int numexpr)
211{
212 struct val *valhead, *valp, v = {.str = NULL, .num = 0};
213 int *ophead, *opp, type, lasttype = 0;
214 char prec[] = {
215 [0] = 0,
216 [VAL] = 0,
217 ['('] = 0,
218 [')'] = 0,
219 ['|'] = 1,
220 ['&'] = 2,
221 ['='] = 3,
222 ['>'] = 3,
223 [GE] = 3,
224 ['<'] = 3,
225 [LE] = 3,
226 [NE] = 3,
227 ['+'] = 4,
228 ['-'] = 4,
229 ['*'] = 5,
230 ['/'] = 5,
231 ['%'] = 5,
232 [':'] = 6,
233 };
234
235 valp = valhead = enreallocarray(3, NULL, numexpr, sizeof(*valp));
236 opp = ophead = enreallocarray(3, NULL, numexpr, sizeof(*opp));
237 for (; *expr; expr++) {
238 switch ((type = lex(*expr, &v))) {
239 case VAL:
240 /* treatment of *expr is not known until
241 * doop(); treat as a string for now */
242 valp->str = *expr;
243 valp++;
244 break;
245 case '(':
246 *opp++ = type;
247 break;
248 case ')':
249 if (lasttype == '(')
250 enprintf(2, "syntax error: empty ( )\n");
251 while (opp > ophead && opp[-1] != '(')
252 doop(ophead, opp--, valhead, valp--);
253 if (opp == ophead)
254 enprintf(2, "syntax error: extra )\n");
255 opp--;
256 break;
257 default: /* operator */
258 if (prec[lasttype])
259 enprintf(2, "syntax error: extra operator\n");
260 while (opp > ophead && prec[opp[-1]] >= prec[type])
261 doop(ophead, opp--, valhead, valp--);
262 *opp++ = type;
263 break;
264 }
265 lasttype = type;
266 v.str = NULL;
267 v.num = 0;
268 }
269 while (opp > ophead)
270 doop(ophead, opp--, valhead, valp--);
271 if (valp == valhead)
272 enprintf(2, "syntax error: missing expression\n");
273 if (--valp > valhead)
274 enprintf(2, "syntax error: extra expression\n");
275
276 if (valp->str)
277 puts(valp->str);
278 else
279 printf("%lld\n", valp->num);
280
281 return (valp->str && *valp->str) || valp->num;
282}
283
284// ?man expr: evaluate expression
285// ?man evaluate a command line expression and print the result
286int
287main(int argc, char *argv[])
288{
289 int ret;
290
291 argv0 = *argv, argv0 ? (argc--, argv++) : (void *)0;
292
293 ret = !parse(argv, argc);
294
295 if (fshut(stdout, "<stdout>"))
296 ret = 3;
297
298 return ret;
299}