Mercurial > ~dholland > hg > tradcpp > index.cgi
annotate eval.c @ 65:161edab0d361
test undef
author | David A. Holland |
---|---|
date | Sun, 31 Mar 2013 02:18:24 -0400 |
parents | 5e24746d8335 |
children | bd1b7a09da89 |
rev | line source |
---|---|
30 | 1 /*- |
2 * Copyright (c) 2010 The NetBSD Foundation, Inc. | |
3 * All rights reserved. | |
4 * | |
5 * This code is derived from software contributed to The NetBSD Foundation | |
6 * by David A. Holland. | |
7 * | |
8 * Redistribution and use in source and binary forms, with or without | |
9 * modification, are permitted provided that the following conditions | |
10 * are met: | |
11 * 1. Redistributions of source code must retain the above copyright | |
12 * notice, this list of conditions and the following disclaimer. | |
13 * 2. Redistributions in binary form must reproduce the above copyright | |
14 * notice, this list of conditions and the following disclaimer in the | |
15 * documentation and/or other materials provided with the distribution. | |
16 * | |
17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS | |
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS | |
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
27 * POSSIBILITY OF SUCH DAMAGE. | |
28 */ | |
29 | |
16 | 30 #include <stdlib.h> |
31 #include <string.h> | |
32 #include <limits.h> | |
33 #include <errno.h> | |
34 | |
35 #include "utils.h" | |
36 #include "array.h" | |
37 #include "mode.h" | |
38 #include "place.h" | |
39 #include "eval.h" | |
40 | |
41 /* | |
42 * e ::= | |
43 * e1 ? e2 : e3 | |
44 * e1 || e2 | |
45 * e1 && e2 | |
46 * e1 | e2 | |
47 * e1 ^ e2 | |
48 * e1 & e2 | |
49 * e1 == e2 | e1 != e2 | |
50 * e1 < e2 | e1 <= e2 | e1 > e2 | e1 >= e2 | |
51 * e1 << e2 | e1 >> e2 | |
52 * e1 + e2 | e1 - e2 | |
53 * e1 * e2 | e1 / e2 | e1 % e2 | |
54 * !e | ~e | -e | +e | |
55 * ( e ) | ident | |
56 */ | |
57 | |
58 enum tokens { | |
59 T_EOF, /* end of input */ | |
60 T_VAL, /* value */ | |
61 T_LPAREN, /* parens */ | |
62 T_RPAREN, | |
63 T_PIPEPIPE, /* operators */ | |
64 T_AMPAMP, | |
65 T_EQEQ, | |
66 T_BANGEQ, | |
67 T_LTEQ, | |
68 T_GTEQ, | |
69 T_LTLT, | |
70 T_GTGT, | |
71 T_QUES, | |
72 T_COLON, | |
73 T_PIPE, | |
74 T_CARET, | |
75 T_AMP, | |
76 T_LT, | |
77 T_GT, | |
78 T_PLUS, | |
79 T_MINUS, | |
80 T_STAR, | |
81 T_SLASH, | |
82 T_PCT, | |
83 T_BANG, | |
84 T_TILDE, | |
85 }; | |
86 | |
87 static const struct { | |
88 char c1, c2; | |
89 enum tokens tok; | |
90 } tokens_2[] = { | |
91 { '|', '|', T_PIPEPIPE }, | |
92 { '&', '&', T_AMPAMP }, | |
93 { '=', '=', T_EQEQ }, | |
94 { '!', '=', T_BANGEQ }, | |
95 { '<', '=', T_LTEQ }, | |
96 { '>', '=', T_GTEQ }, | |
97 { '<', '<', T_LTLT }, | |
98 { '>', '>', T_GTGT }, | |
99 }; | |
100 static const unsigned num_tokens_2 = HOWMANY(tokens_2); | |
101 | |
102 static const struct { | |
103 char c1; | |
104 enum tokens tok; | |
105 } tokens_1[] = { | |
106 { '?', T_QUES }, | |
107 { ':', T_COLON }, | |
108 { '|', T_PIPE }, | |
109 { '^', T_CARET }, | |
110 { '&', T_AMP }, | |
111 { '<', T_LT }, | |
112 { '>', T_GT }, | |
113 { '+', T_PLUS }, | |
114 { '-', T_MINUS }, | |
115 { '*', T_STAR }, | |
116 { '/', T_SLASH }, | |
117 { '%', T_PCT }, | |
118 { '!', T_BANG }, | |
119 { '~', T_TILDE }, | |
120 { '(', T_LPAREN }, | |
121 { ')', T_RPAREN }, | |
122 }; | |
123 static const unsigned num_tokens_1 = HOWMANY(tokens_1); | |
124 | |
125 struct token { | |
126 struct place place; | |
127 enum tokens tok; | |
128 int val; | |
129 }; | |
47
2e25e55dba6b
Fix inline usage as per the version in dholland-make2.
David A. Holland
parents:
39
diff
changeset
|
130 DECLARRAY(token, static __unused); |
2e25e55dba6b
Fix inline usage as per the version in dholland-make2.
David A. Holland
parents:
39
diff
changeset
|
131 DEFARRAY(token, static); |
16 | 132 |
133 static struct tokenarray tokens; | |
134 | |
135 static | |
136 struct token * | |
137 token_create(const struct place *p, enum tokens tok, int val) | |
138 { | |
139 struct token *t; | |
140 | |
141 t = domalloc(sizeof(*t)); | |
142 t->place = *p; | |
143 t->tok = tok; | |
144 t->val = val; | |
145 return t; | |
146 } | |
147 | |
148 static | |
149 void | |
150 token_destroy(struct token *t) | |
151 { | |
39
337110e7240a
Pass the size to free; it makes debug checking easier.
David A. Holland
parents:
38
diff
changeset
|
152 dofree(t, sizeof(*t)); |
16 | 153 } |
154 | |
155 DESTROYALL_ARRAY(token, ); | |
156 | |
157 static | |
158 bool | |
159 isuop(enum tokens tok) | |
160 { | |
161 switch (tok) { | |
162 case T_BANG: | |
163 case T_TILDE: | |
164 case T_MINUS: | |
165 case T_PLUS: | |
166 return true; | |
167 default: | |
168 break; | |
169 } | |
170 return false; | |
171 } | |
172 | |
173 static | |
174 bool | |
175 isbop(enum tokens tok) | |
176 { | |
177 switch (tok) { | |
178 case T_EOF: | |
179 case T_VAL: | |
180 case T_LPAREN: | |
181 case T_RPAREN: | |
182 case T_COLON: | |
183 case T_QUES: | |
184 case T_BANG: | |
185 case T_TILDE: | |
186 return false; | |
187 default: | |
188 break; | |
189 } | |
190 return true; | |
191 } | |
192 | |
193 static | |
194 bool | |
195 isop(enum tokens tok) | |
196 { | |
197 switch (tok) { | |
198 case T_EOF: | |
199 case T_VAL: | |
200 case T_LPAREN: | |
201 case T_RPAREN: | |
202 return false; | |
203 default: | |
204 break; | |
205 } | |
206 return true; | |
207 } | |
208 | |
209 static | |
210 int | |
211 getprec(enum tokens tok) | |
212 { | |
213 switch (tok) { | |
214 case T_STAR: case T_SLASH: case T_PCT: return 0; | |
215 case T_PLUS: case T_MINUS: return 1; | |
216 case T_LTLT: case T_GTGT: return 2; | |
217 case T_LT: case T_LTEQ: case T_GT: case T_GTEQ: return 3; | |
218 case T_EQEQ: case T_BANGEQ: return 4; | |
219 case T_AMP: return 5; | |
220 case T_CARET: return 6; | |
221 case T_PIPE: return 7; | |
222 case T_AMPAMP: return 8; | |
223 case T_PIPEPIPE: return 9; | |
224 default: break; | |
225 } | |
226 return 10; | |
227 } | |
228 | |
229 static | |
230 bool | |
231 looser(enum tokens t1, enum tokens t2) | |
232 { | |
233 return getprec(t1) > getprec(t2); | |
234 } | |
235 | |
236 static | |
237 int | |
238 eval_uop(enum tokens op, int val) | |
239 { | |
240 switch (op) { | |
241 case T_BANG: val = !val; break; | |
242 case T_TILDE: val = (int)~(unsigned)val; break; | |
243 case T_MINUS: val = -val; break; | |
244 case T_PLUS: break; | |
245 default: assert(0); break; | |
246 } | |
247 return val; | |
248 } | |
249 | |
250 static | |
251 int | |
28 | 252 eval_bop(struct place *p, int lv, enum tokens op, int rv) |
16 | 253 { |
254 unsigned mask; | |
255 | |
256 switch (op) { | |
257 case T_PIPEPIPE: return lv || rv; | |
258 case T_AMPAMP: return lv && rv; | |
259 case T_PIPE: return (int)((unsigned)lv | (unsigned)rv); | |
260 case T_CARET: return (int)((unsigned)lv ^ (unsigned)rv); | |
261 case T_AMP: return (int)((unsigned)lv & (unsigned)rv); | |
262 case T_EQEQ: return lv == rv; | |
263 case T_BANGEQ: return lv != rv; | |
264 case T_LT: return lv < rv; | |
265 case T_GT: return lv > rv; | |
266 case T_LTEQ: return lv <= rv; | |
267 case T_GTEQ: return lv >= rv; | |
268 | |
269 case T_LTLT: | |
270 case T_GTGT: | |
271 if (rv < 0) { | |
272 complain(p, "Negative bit-shift"); | |
273 complain_fail(); | |
274 rv = 0; | |
275 } | |
276 if ((unsigned)rv >= CHAR_BIT * sizeof(unsigned)) { | |
277 complain(p, "Bit-shift farther than type width"); | |
278 complain_fail(); | |
279 rv = 0; | |
280 } | |
281 if (op == T_LTLT) { | |
282 return (int)((unsigned)lv << (unsigned)rv); | |
283 } | |
284 mask = ((unsigned)-1) << (CHAR_BIT * sizeof(unsigned) - rv); | |
285 lv = (int)(((unsigned)lv >> (unsigned)rv) | mask); | |
286 return lv; | |
287 | |
288 case T_MINUS: | |
289 if (rv == INT_MIN) { | |
290 if (lv == INT_MIN) { | |
291 return 0; | |
292 } | |
293 lv--; | |
294 rv++; | |
295 } | |
296 rv = -rv; | |
297 /* FALLTHROUGH */ | |
298 case T_PLUS: | |
299 if (rv > 0 && lv > (INT_MAX - rv)) { | |
300 complain(p, "Integer overflow"); | |
301 complain_fail(); | |
302 return INT_MAX; | |
303 } | |
304 if (rv < 0 && lv < (INT_MIN - rv)) { | |
305 complain(p, "Integer underflow"); | |
306 complain_fail(); | |
307 return INT_MIN; | |
308 } | |
309 return lv + rv; | |
310 | |
311 case T_STAR: | |
312 if (rv == 0) { | |
313 return 0; | |
314 } | |
315 if (rv == 1) { | |
316 return lv; | |
317 } | |
318 if (rv == -1 && lv == INT_MIN) { | |
319 lv++; | |
320 lv = -lv; | |
321 if (lv == INT_MAX) { | |
322 complain(p, "Integer overflow"); | |
323 complain_fail(); | |
324 return INT_MAX; | |
325 } | |
326 lv++; | |
327 return lv; | |
328 } | |
329 if (lv == INT_MIN && rv < 0) { | |
330 complain(p, "Integer overflow"); | |
331 complain_fail(); | |
332 return INT_MAX; | |
333 } | |
334 if (lv == INT_MIN && rv > 0) { | |
335 complain(p, "Integer underflow"); | |
336 complain_fail(); | |
337 return INT_MIN; | |
338 } | |
339 if (rv < 0) { | |
340 rv = -rv; | |
341 lv = -lv; | |
342 } | |
343 if (lv > 0 && lv > INT_MAX / rv) { | |
344 complain(p, "Integer overflow"); | |
345 complain_fail(); | |
346 return INT_MAX; | |
347 } | |
348 if (lv < 0 && lv < INT_MIN / rv) { | |
349 complain(p, "Integer underflow"); | |
350 complain_fail(); | |
351 return INT_MIN; | |
352 } | |
353 return lv * rv; | |
354 | |
355 case T_SLASH: | |
356 if (rv == 0) { | |
357 complain(p, "Division by zero"); | |
358 complain_fail(); | |
359 return 0; | |
360 } | |
361 return lv / rv; | |
362 | |
363 case T_PCT: | |
364 if (rv == 0) { | |
365 complain(p, "Modulus by zero"); | |
366 complain_fail(); | |
367 return 0; | |
368 } | |
369 return lv % rv; | |
370 | |
371 default: assert(0); break; | |
372 } | |
373 return 0; | |
374 } | |
375 | |
376 static | |
377 void | |
378 tryreduce(void) | |
379 { | |
380 unsigned num; | |
381 struct token *t1, *t2, *t3, *t4, *t5, *t6; | |
382 | |
383 while (1) { | |
384 num = tokenarray_num(&tokens); | |
385 t1 = (num >= 1) ? tokenarray_get(&tokens, num-1) : NULL; | |
386 t2 = (num >= 2) ? tokenarray_get(&tokens, num-2) : NULL; | |
387 t3 = (num >= 3) ? tokenarray_get(&tokens, num-3) : NULL; | |
388 | |
389 if (num >= 3 && | |
390 t3->tok == T_LPAREN && | |
391 t2->tok == T_VAL && | |
392 t1->tok == T_RPAREN) { | |
393 /* (x) -> x */ | |
394 t2->place = t3->place; | |
395 token_destroy(t1); | |
396 token_destroy(t3); | |
397 tokenarray_remove(&tokens, num-1); | |
398 tokenarray_remove(&tokens, num-3); | |
399 continue; | |
400 } | |
401 | |
402 if (num >= 2 && | |
403 (num == 2 || isop(t3->tok) || t3->tok == T_LPAREN) && | |
404 isuop(t2->tok) && | |
405 t1->tok == T_VAL) { | |
406 /* unary operator */ | |
407 t1->val = eval_uop(t2->tok, t1->val); | |
408 t1->place = t2->place; | |
409 token_destroy(t2); | |
410 tokenarray_remove(&tokens, num-2); | |
411 continue; | |
412 } | |
413 if (num >= 2 && | |
414 (num == 2 || isop(t3->tok) || t3->tok == T_RPAREN) && | |
415 t2->tok != T_LPAREN && t2->tok != T_VAL && | |
416 t1->tok == T_VAL) { | |
417 complain(&t2->place, "Invalid unary operator"); | |
418 complain_fail(); | |
419 token_destroy(t2); | |
420 tokenarray_remove(&tokens, num-2); | |
421 continue; | |
422 } | |
423 | |
424 | |
425 t4 = (num >= 4) ? tokenarray_get(&tokens, num-4) : NULL; | |
426 | |
427 if (num >= 4 && | |
428 t4->tok == T_VAL && | |
429 isbop(t3->tok) && | |
430 t2->tok == T_VAL && | |
431 (isbop(t1->tok) || !isop(t1->tok))) { | |
432 /* binary operator */ | |
433 if (looser(t1->tok, t3->tok)) { | |
434 t4->val = eval_bop(&t3->place, | |
435 t4->val, t3->tok, t2->val); | |
436 token_destroy(t2); | |
437 token_destroy(t3); | |
438 tokenarray_remove(&tokens, num-2); | |
439 tokenarray_remove(&tokens, num-3); | |
440 continue; | |
441 } | |
442 break; | |
443 } | |
444 | |
445 t5 = (num >= 5) ? tokenarray_get(&tokens, num-5) : NULL; | |
446 t6 = (num >= 6) ? tokenarray_get(&tokens, num-6) : NULL; | |
447 | |
448 if (num >=6 && | |
449 t6->tok == T_VAL && | |
450 t5->tok == T_QUES && | |
451 t4->tok == T_VAL && | |
452 t3->tok == T_COLON && | |
453 t2->tok == T_VAL && | |
454 !isop(t1->tok)) { | |
455 /* conditional expression */ | |
456 t6->val = t6->val ? t4->val : t2->val; | |
457 token_destroy(t2); | |
458 token_destroy(t3); | |
459 token_destroy(t4); | |
460 token_destroy(t5); | |
461 tokenarray_remove(&tokens, num-2); | |
462 tokenarray_remove(&tokens, num-3); | |
463 tokenarray_remove(&tokens, num-4); | |
464 tokenarray_remove(&tokens, num-5); | |
465 continue; | |
466 } | |
467 | |
468 if (num >= 2 && | |
469 t2->tok == T_LPAREN && | |
470 t1->tok == T_RPAREN) { | |
471 complain(&t1->place, "Value expected within ()"); | |
472 complain_fail(); | |
473 t1->tok = T_VAL; | |
474 t1->val = 0; | |
475 token_destroy(t1); | |
476 tokenarray_remove(&tokens, num-1); | |
477 continue; | |
478 } | |
479 | |
480 if (num >= 2 && | |
481 t2->tok == T_VAL && | |
482 t1->tok == T_VAL) { | |
483 complain(&t1->place, "Operator expected"); | |
484 complain_fail(); | |
485 token_destroy(t1); | |
486 tokenarray_remove(&tokens, num-1); | |
487 continue; | |
488 } | |
489 | |
490 if (num >= 2 && | |
491 isop(t2->tok) && | |
492 t1->tok == T_EOF) { | |
493 complain(&t1->place, "Value expected after operator"); | |
494 complain_fail(); | |
495 token_destroy(t2); | |
496 tokenarray_remove(&tokens, num-2); | |
497 continue; | |
498 } | |
499 | |
500 if (num == 2 && | |
501 t2->tok == T_VAL && | |
502 t1->tok == T_RPAREN) { | |
503 complain(&t1->place, "Excess right parenthesis"); | |
504 complain_fail(); | |
505 token_destroy(t1); | |
506 tokenarray_remove(&tokens, num-1); | |
507 continue; | |
508 } | |
509 | |
510 if (num == 3 && | |
511 t3->tok == T_LPAREN && | |
512 t2->tok == T_VAL && | |
513 t1->tok == T_EOF) { | |
514 complain(&t1->place, "Unclosed left parenthesis"); | |
515 complain_fail(); | |
516 token_destroy(t3); | |
517 tokenarray_remove(&tokens, num-3); | |
518 continue; | |
519 } | |
520 | |
521 if (num == 2 && | |
522 t2->tok == T_VAL && | |
523 t1->tok == T_EOF) { | |
524 /* accepting state */ | |
525 break; | |
526 } | |
527 | |
528 if (num >= 1 && | |
529 t1->tok == T_EOF) { | |
530 /* any other configuration at eof is an error */ | |
531 complain(&t1->place, "Parse error"); | |
532 complain_fail(); | |
533 break; | |
534 } | |
535 | |
536 /* otherwise, wait for more input */ | |
537 break; | |
538 } | |
539 } | |
540 | |
541 static | |
542 void | |
543 token(struct place *p, enum tokens tok, int val) | |
544 { | |
545 struct token *t; | |
546 | |
547 t = token_create(p, tok, val); | |
548 | |
549 tokenarray_add(&tokens, t, NULL); | |
550 tryreduce(); | |
551 } | |
552 | |
553 static | |
554 int | |
555 wordval(struct place *p, char *word) | |
556 { | |
557 unsigned long val; | |
558 char *t; | |
559 | |
560 if (word[0] >= '0' && word[0] <= '9') { | |
561 errno = 0; | |
562 val = strtoul(word, &t, 0); | |
563 if (errno || *t != '\0') { | |
564 complain(p, "Invalid integer constant"); | |
565 complain_fail(); | |
566 return 0; | |
567 } | |
568 if (val > INT_MAX) { | |
569 complain(p, "Integer constant too large"); | |
570 complain_fail(); | |
571 return INT_MAX; | |
572 } | |
573 return val; | |
574 } | |
575 | |
576 /* if it's a symbol, warn and substitute 0. */ | |
577 if (warns.undef) { | |
578 complain(p, "Warning: value of undefined symbol %s is 0", | |
579 word); | |
580 if (mode.werror) { | |
581 complain_fail(); | |
582 } | |
583 } | |
584 return 0; | |
585 } | |
586 | |
587 static | |
588 bool | |
589 check_word(struct place *p, char *expr, size_t pos, size_t *len_ret) | |
590 { | |
591 size_t len; | |
592 int val; | |
593 char tmp; | |
594 | |
595 if (!strchr(alnum, expr[pos])) { | |
596 return false; | |
597 } | |
598 len = strspn(expr + pos, alnum); | |
599 tmp = expr[pos + len]; | |
600 expr[pos + len] = '\0'; | |
601 val = wordval(p, expr + pos); | |
602 expr[pos + len] = tmp; | |
603 token(p, T_VAL, val); | |
604 *len_ret = len; | |
605 return true; | |
606 } | |
607 | |
608 static | |
609 bool | |
610 check_tokens_2(struct place *p, char *expr, size_t pos) | |
611 { | |
612 unsigned i; | |
613 | |
614 for (i=0; i<num_tokens_2; i++) { | |
615 if (expr[pos] == tokens_2[i].c1 && | |
616 expr[pos+1] == tokens_2[i].c2) { | |
617 token(p, tokens_2[i].tok, 0); | |
618 return true; | |
619 } | |
620 } | |
621 return false; | |
622 } | |
623 | |
624 static | |
625 bool | |
626 check_tokens_1(struct place *p, char *expr, size_t pos) | |
627 { | |
628 unsigned i; | |
629 | |
630 for (i=0; i<num_tokens_1; i++) { | |
631 if (expr[pos] == tokens_1[i].c1) { | |
632 token(p, tokens_1[i].tok, 0); | |
633 return true; | |
634 } | |
635 } | |
636 return false; | |
637 } | |
638 | |
639 static | |
640 void | |
641 tokenize(struct place *p, char *expr) | |
642 { | |
643 size_t pos, len; | |
644 | |
645 pos = 0; | |
646 while (expr[pos] != '\0') { | |
647 len = strspn(expr+pos, ws); | |
648 pos += len; | |
649 p->column += len; | |
63 | 650 /* trailing whitespace is supposed to have been pruned */ |
651 assert(expr[pos] != '\0'); | |
16 | 652 if (check_word(p, expr, pos, &len)) { |
653 pos += len; | |
654 p->column += len; | |
655 continue; | |
656 } | |
657 if (check_tokens_2(p, expr, pos)) { | |
658 pos += 2; | |
659 p->column += 2; | |
660 continue; | |
661 } | |
662 if (check_tokens_1(p, expr, pos)) { | |
663 pos++; | |
664 p->column++; | |
665 continue; | |
666 } | |
667 complain(p, "Invalid character %u in #if-expression", | |
668 (unsigned char)expr[pos]); | |
669 complain_fail(); | |
670 pos++; | |
671 p->column++; | |
672 } | |
673 token(p, T_EOF, 0); | |
674 } | |
675 | |
676 bool | |
677 eval(struct place *p, char *expr) | |
678 { | |
679 struct token *t1, *t2; | |
680 unsigned num; | |
681 bool result; | |
682 | |
683 tokenarray_init(&tokens); | |
684 tokenize(p, expr); | |
685 | |
686 result = false; | |
687 num = tokenarray_num(&tokens); | |
688 if (num == 2) { | |
689 t1 = tokenarray_get(&tokens, num-1); | |
690 t2 = tokenarray_get(&tokens, num-2); | |
691 if (t1->tok == T_VAL && | |
692 t2->tok == T_EOF) { | |
693 result = t1->val != 0; | |
694 } | |
695 } | |
696 | |
697 tokenarray_destroyall(&tokens); | |
698 tokenarray_cleanup(&tokens); | |
699 return result; | |
700 } |