ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/calexpr.c
Revision: 2.49
Committed: Mon Feb 26 18:16:35 2024 UTC (2 months, 2 weeks ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.48: +2 -10 lines
Log Message:
perf: Added shortcut to avoid extra call in eargument(), which was bottleneck

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: calexpr.c,v 2.48 2024/02/25 04:41:44 greg Exp $";
3 #endif
4 /*
5 * Compute data values using expression parser
6 *
7 * 7/1/85 Greg Ward
8 *
9 * 11/11/85 Made channel input conditional with (INCHAN) compiles.
10 *
11 * 4/2/86 Added conditional compiles for function definitions (FUNCTION).
12 *
13 * 1/29/87 Made variables conditional (VARIABLE)
14 *
15 * 5/19/88 Added constant subexpression elimination (RCONST)
16 *
17 * 2/19/03 Eliminated conditional compiles in favor of esupport extern.
18 */
19
20 #include "copyright.h"
21
22 #include <ctype.h>
23 #include <errno.h>
24 #include <math.h>
25 #include <stdlib.h>
26
27 #include "rtmisc.h"
28 #include "rtio.h"
29 #include "rterror.h"
30 #include "calcomp.h"
31
32 #define MAXLINE 256 /* maximum line length */
33
34 #define newnode() (EPNODE *)ecalloc(1, sizeof(EPNODE))
35
36 #define isdecimal(c) (isdigit(c) | ((c) == '.'))
37
38 #define envalue(ep) ((ep)->type==NUM ? (ep)->v.num : evalue(ep))
39
40 static double euminus(EPNODE *), enumber(EPNODE *);
41 static double echannel(EPNODE *);
42 static double eadd(EPNODE *), esubtr(EPNODE *),
43 emult(EPNODE *), edivi(EPNODE *),
44 epow(EPNODE *);
45 static double ebotch(EPNODE *);
46
47 unsigned int esupport = /* what to support */
48 E_VARIABLE | E_FUNCTION ;
49
50 int eofc = 0; /* optional end-of-file character */
51 int nextc; /* lookahead character */
52
53 double (*eoper[])(EPNODE *) = { /* expression operations */
54 ebotch,
55 evariable,
56 enumber,
57 euminus,
58 echannel,
59 efunc,
60 eargument,
61 ebotch,
62 ebotch,
63 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
64 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65 emult,
66 eadd,
67 0,
68 esubtr,
69 0,
70 edivi,
71 0,0,0,0,0,0,0,0,0,0,
72 ebotch,
73 0,0,
74 ebotch,
75 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
76 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
77 epow,
78 };
79
80 static FILE *infp; /* input file pointer */
81 static char *linbuf; /* line buffer */
82 static char *infile; /* input file name */
83 static int lineno; /* input line number */
84 static int linepos; /* position in buffer */
85
86
87 EPNODE *
88 eparse( /* parse an expression string */
89 char *expr
90 )
91 {
92 EPNODE *ep;
93
94 initstr(expr, NULL, 0);
95 curfunc = NULL;
96 ep = getE1();
97 if (nextc != EOF)
98 syntax("unexpected character");
99 return(ep);
100 }
101
102
103 double
104 eval( /* evaluate an expression string */
105 char *expr
106 )
107 {
108 int prev_support = esupport;
109 EPNODE *ep;
110 double rval;
111
112 esupport &= ~E_RCONST; /* don't bother reducing constant expr */
113 ep = eparse(expr);
114 esupport = prev_support; /* as you were */
115 rval = evalue(ep);
116 epfree(ep,1);
117 return(rval);
118 }
119
120
121 int
122 epcmp( /* compare two expressions for equivalence */
123 EPNODE *ep1,
124 EPNODE *ep2
125 )
126 {
127 double d;
128
129 if (ep1->type != ep2->type)
130 return(1);
131
132 switch (ep1->type) {
133
134 case VAR:
135 return(ep1->v.ln != ep2->v.ln);
136
137 case NUM:
138 if (ep2->v.num == 0)
139 return(ep1->v.num != 0);
140 d = ep1->v.num / ep2->v.num;
141 return((d > 1.000000000001) | (d < 0.999999999999));
142
143 case CHAN:
144 case ARG:
145 return(ep1->v.chan != ep2->v.chan);
146
147 case '=':
148 case ':':
149 return(epcmp(ep1->v.kid->sibling, ep2->v.kid->sibling));
150
151 case CLKT:
152 case SYM: /* should never get this one */
153 return(0);
154
155 default:
156 ep1 = ep1->v.kid;
157 ep2 = ep2->v.kid;
158 while (ep1 != NULL) {
159 if (ep2 == NULL)
160 return(1);
161 if (epcmp(ep1, ep2))
162 return(1);
163 ep1 = ep1->sibling;
164 ep2 = ep2->sibling;
165 }
166 return(ep2 != NULL);
167 }
168 }
169
170
171 void
172 epfree( /* free a parse tree */
173 EPNODE *epar,
174 int frep
175 )
176 {
177 EPNODE *ep;
178
179 switch (epar->type) {
180
181 case VAR:
182 varfree(epar->v.ln);
183 break;
184
185 case SYM:
186 freestr(epar->v.name);
187 break;
188
189 case NUM:
190 case CHAN:
191 case ARG:
192 case CLKT:
193 break;
194
195 default:
196 if (epar->nkids < 0) {
197 ep = epar->v.kid - epar->nkids;
198 while (ep > epar->v.kid)
199 epfree(--ep, 0);
200 efree(ep); /* free array space */
201 } else
202 while ((ep = epar->v.kid) != NULL) {
203 epar->v.kid = ep->sibling;
204 epfree(ep, 1);
205 }
206 break;
207
208 }
209 if (frep)
210 efree(epar);
211 }
212
213
214 static void
215 epflatten( /* flatten hierarchies for '+', '*' */
216 EPNODE *epar
217 )
218 {
219 EPNODE *ep;
220
221 if (epar->nkids < 0) /* can't handle array allocations */
222 return;
223
224 for (ep = epar->v.kid; ep != NULL; ep = ep->sibling)
225 while (ep->type == epar->type && ep->nkids > 0) {
226 EPNODE *ep1 = ep->v.kid;
227 while (ep1->sibling != NULL)
228 ep1 = ep1->sibling;
229 ep1->sibling = ep->sibling;
230 epar->nkids += ep->nkids - 1;
231 ep1 = ep->v.kid;
232 *ep = *ep1;
233 efree(ep1); /* not epfree()! */
234 }
235 }
236
237
238 void
239 epoptimize( /* flatten operations, lists -> arrays */
240 EPNODE *epar
241 )
242 {
243 EPNODE *ep;
244
245 if ((epar->type == '+') | (epar->type == '*'))
246 epflatten(epar); /* flatten associative operations */
247
248 if (epar->nkids) /* do children if any */
249 for (ep = epar->v.kid; ep != NULL; ep = ep->sibling)
250 epoptimize(ep);
251
252 if (epar->nkids > 4) { /* make list into array if > 4 kids */
253 int n = 1;
254 epar->v.kid = (EPNODE *)erealloc(epar->v.kid,
255 sizeof(EPNODE)*epar->nkids);
256 while (n < epar->nkids) {
257 ep = epar->v.kid[n-1].sibling;
258 epar->v.kid[n] = *ep;
259 efree(ep); /* not epfree()! */
260 epar->v.kid[n-1].sibling = epar->v.kid + n;
261 n++;
262 }
263 epar->nkids = -n;
264 }
265 }
266
267 /* the following used to be a switch */
268 static double
269 enumber(
270 EPNODE *ep
271 )
272 {
273 return(ep->v.num);
274 }
275
276 static double
277 euminus(
278 EPNODE *ep
279 )
280 {
281 EPNODE *ep1 = ep->v.kid;
282
283 return(-evalue(ep1));
284 }
285
286 static double
287 echannel(
288 EPNODE *ep
289 )
290 {
291 return(chanvalue(ep->v.chan));
292 }
293
294 static double
295 eadd(
296 EPNODE *ep
297 )
298 {
299 double sum = 0;
300 EPNODE *ep1 = ep->v.kid;
301
302 do
303 sum += envalue(ep1);
304 while ((ep1 = ep1->sibling) != NULL);
305
306 return(sum);
307 }
308
309 static double
310 esubtr(
311 EPNODE *ep
312 )
313 {
314 EPNODE *ep1 = ep->v.kid;
315 EPNODE *ep2 = ep1->sibling;
316
317 return(envalue(ep1) - envalue(ep2));
318 }
319
320 static double
321 emult(
322 EPNODE *ep
323 )
324 {
325 double prod = 1;
326 EPNODE *ep1 = ep->v.kid;
327
328 do
329 prod *= envalue(ep1);
330 while ((ep1 = ep1->sibling) != NULL);
331
332 return(prod);
333 }
334
335 static double
336 edivi(
337 EPNODE *ep
338 )
339 {
340 EPNODE *ep1 = ep->v.kid;
341 double den = evalue(ep1->sibling);
342
343 if (den == 0.0) {
344 wputs("Division by zero\n");
345 errno = ERANGE;
346 return(0.0);
347 }
348 return(envalue(ep1) / den);
349 }
350
351 static double
352 epow(
353 EPNODE *ep
354 )
355 {
356 EPNODE *ep1 = ep->v.kid;
357 double d;
358 int lasterrno;
359
360 lasterrno = errno;
361 errno = 0;
362 d = pow(evalue(ep1), evalue(ep1->sibling));
363 #ifdef isnan
364 if (errno == 0) {
365 if (isnan(d))
366 errno = EDOM;
367 else if (isinf(d))
368 errno = ERANGE;
369 }
370 #endif
371 if ((errno == EDOM) | (errno == ERANGE)) {
372 wputs("Illegal power\n");
373 return(0.0);
374 }
375 errno = lasterrno;
376 return(d);
377 }
378
379 static double
380 ebotch(
381 EPNODE *ep
382 )
383 {
384 eputs("Bad expression!\n");
385 quit(1);
386 return 0.0; /* pro forma return */
387 }
388
389
390 EPNODE *
391 ekid( /* return pointer to a node's nth kid */
392 EPNODE *ep,
393 int n
394 )
395 {
396 if (ep->nkids < 0) { /* allocated array? */
397 if (n >= -ep->nkids)
398 return(NULL);
399 return(ep->v.kid + n);
400 }
401 ep = ep->v.kid; /* else get from list */
402 while (n-- > 0)
403 if ((ep = ep->sibling) == NULL)
404 break;
405 return(ep);
406 }
407
408
409 void
410 initfile( /* prepare input file */
411 FILE *fp,
412 char *fn,
413 int ln
414 )
415 {
416 static char inpbuf[MAXLINE];
417
418 infp = fp;
419 linbuf = inpbuf;
420 infile = fn;
421 lineno = ln;
422 linepos = 0;
423 inpbuf[0] = '\0';
424 scan();
425 }
426
427
428 void
429 initstr( /* prepare input string */
430 char *s,
431 char *fn,
432 int ln
433 )
434 {
435 infp = NULL;
436 infile = fn;
437 lineno = ln;
438 linbuf = s;
439 linepos = 0;
440 scan();
441 }
442
443
444 void
445 getscanpos( /* return current scan position */
446 char **fnp,
447 int *lnp,
448 char **spp,
449 FILE **fpp
450 )
451 {
452 if (fnp != NULL) *fnp = infile;
453 if (lnp != NULL) *lnp = lineno;
454 if (spp != NULL) *spp = linbuf+linepos;
455 if (fpp != NULL) *fpp = infp;
456 }
457
458
459 int
460 scan(void) /* scan next character, return literal next */
461 {
462 int lnext = 0;
463
464 do {
465 if (linbuf[linepos] == '\0')
466 if (infp == NULL || fgets(linbuf, MAXLINE, infp) == NULL)
467 nextc = EOF;
468 else {
469 nextc = linbuf[0];
470 lineno++;
471 linepos = 1;
472 }
473 else
474 nextc = linbuf[linepos++];
475 if (!lnext)
476 lnext = nextc;
477 if (nextc == eofc) {
478 nextc = EOF;
479 break;
480 }
481 if (nextc == '{') {
482 scan();
483 while (nextc != '}')
484 if (nextc == EOF)
485 syntax("'}' expected");
486 else
487 scan();
488 scan();
489 }
490 } while (isspace(nextc));
491 return(lnext);
492 }
493
494
495 char *
496 long2ascii( /* convert long to ascii */
497 long l
498 )
499 {
500 static char buf[16];
501 char *cp;
502 int neg = 0;
503
504 if (l == 0)
505 return("0");
506 if (l < 0) {
507 l = -l;
508 neg++;
509 }
510 cp = buf + sizeof(buf);
511 *--cp = '\0';
512 while (l) {
513 *--cp = l % 10 + '0';
514 l /= 10;
515 }
516 if (neg)
517 *--cp = '-';
518 return(cp);
519 }
520
521
522 void
523 syntax( /* report syntax error and quit */
524 char *err
525 )
526 {
527 int i;
528
529 if ((infile != NULL) | (lineno != 0)) {
530 if (infile != NULL) eputs(infile);
531 if (lineno != 0) {
532 eputs(infile != NULL ? ", line " : "line ");
533 eputs(long2ascii((long)lineno));
534 }
535 eputs(":\n");
536 }
537 eputs(linbuf);
538 if (linbuf[strlen(linbuf)-1] != '\n')
539 eputs("\n");
540 for (i = 0; i < linepos-1; i++)
541 eputs(linbuf[i] == '\t' ? "\t" : " ");
542 eputs("^ ");
543 eputs(err);
544 eputs("\n");
545 quit(1);
546 }
547
548
549 void
550 addekid( /* add a child to ep */
551 EPNODE *ep,
552 EPNODE *ek
553 )
554 {
555 if (ep->nkids < 0) {
556 eputs("Cannot add kid to EPNODE array\n");
557 quit(1);
558 }
559 ep->nkids++;
560 if (ep->v.kid == NULL)
561 ep->v.kid = ek;
562 else {
563 for (ep = ep->v.kid; ep->sibling != NULL; ep = ep->sibling)
564 ;
565 ep->sibling = ek;
566 }
567 ek->sibling = NULL; /* shouldn't be necessary */
568 }
569
570
571 char *
572 getname(void) /* scan an identifier */
573 {
574 static char str[RMAXWORD+1];
575 int i, lnext;
576
577 lnext = nextc;
578 for (i = 0; i < RMAXWORD && isid(lnext); i++, lnext = scan())
579 str[i] = lnext;
580 str[i] = '\0';
581 while (isid(lnext)) /* skip rest of name */
582 lnext = scan();
583
584 return(str);
585 }
586
587
588 int
589 getinum(void) /* scan a positive integer */
590 {
591 int n, lnext;
592
593 n = 0;
594 lnext = nextc;
595 while (isdigit(lnext)) {
596 n = n * 10 + lnext - '0';
597 lnext = scan();
598 }
599 return(n);
600 }
601
602
603 double
604 getnum(void) /* scan a positive float */
605 {
606 int i, lnext;
607 char str[RMAXWORD+1];
608
609 i = 0;
610 lnext = nextc;
611 while (isdigit(lnext) && i < RMAXWORD) {
612 str[i++] = lnext;
613 lnext = scan();
614 }
615 if ((lnext == '.') & (i < RMAXWORD)) {
616 str[i++] = lnext;
617 lnext = scan();
618 if (i == 1 && !isdigit(lnext))
619 syntax("badly formed number");
620 while (isdigit(lnext) && i < RMAXWORD) {
621 str[i++] = lnext;
622 lnext = scan();
623 }
624 }
625 if ((lnext == 'e') | (lnext == 'E') && i < RMAXWORD) {
626 str[i++] = lnext;
627 lnext = scan();
628 if ((lnext == '-') | (lnext == '+') && i < RMAXWORD) {
629 str[i++] = lnext;
630 lnext = scan();
631 }
632 if (!isdigit(lnext))
633 syntax("missing exponent");
634 while (isdigit(lnext) && i < RMAXWORD) {
635 str[i++] = lnext;
636 lnext = scan();
637 }
638 }
639 str[i] = '\0';
640
641 return(atof(str));
642 }
643
644
645 EPNODE *
646 getE1(void) /* E1 -> E1 ADDOP E2 */
647 /* E2 */
648 {
649 EPNODE *ep1, *ep2;
650
651 ep1 = getE2();
652 while ((nextc == '+') | (nextc == '-')) {
653 ep2 = newnode();
654 ep2->type = nextc;
655 scan();
656 addekid(ep2, ep1);
657 addekid(ep2, getE2());
658 if (esupport&E_RCONST &&
659 (ep1->type == NUM) & (ep1->sibling->type == NUM))
660 ep2 = rconst(ep2);
661 ep1 = ep2;
662 }
663 return(ep1);
664 }
665
666
667 EPNODE *
668 getE2(void) /* E2 -> E2 MULOP E3 */
669 /* E3 */
670 {
671 EPNODE *ep1, *ep2;
672
673 ep1 = getE3();
674 while ((nextc == '*') | (nextc == '/')) {
675 ep2 = newnode();
676 ep2->type = nextc;
677 scan();
678 addekid(ep2, ep1);
679 addekid(ep2, getE3());
680 if (esupport&E_RCONST) {
681 EPNODE *ep3 = ep1->sibling;
682 if ((ep1->type == NUM) & (ep3->type == NUM)) {
683 ep2 = rconst(ep2);
684 } else if (ep3->type == NUM) {
685 if (ep2->type == '/') {
686 if (ep3->v.num == 0)
687 syntax("divide by zero constant");
688 ep2->type = '*'; /* for speed */
689 ep3->v.num = 1./ep3->v.num;
690 } else if (ep3->v.num == 0) {
691 ep1->sibling = NULL; /* (E2 * 0) */
692 epfree(ep2,1);
693 ep2 = ep3;
694 }
695 } else if (ep1->type == NUM && ep1->v.num == 0) {
696 epfree(ep3,1); /* (0 * E3) or (0 / E3) */
697 ep1->sibling = NULL;
698 efree(ep2);
699 ep2 = ep1;
700 }
701 }
702 ep1 = ep2;
703 }
704 return(ep1);
705 }
706
707
708 EPNODE *
709 getE3(void) /* E3 -> E4 ^ E3 */
710 /* E4 */
711 {
712 EPNODE *ep1, *ep2;
713
714 ep1 = getE4();
715 if (nextc != '^')
716 return(ep1);
717 ep2 = newnode();
718 ep2->type = nextc;
719 scan();
720 addekid(ep2, ep1);
721 addekid(ep2, getE3());
722 if (esupport&E_RCONST) {
723 EPNODE *ep3 = ep1->sibling;
724 if ((ep1->type == NUM) & (ep3->type == NUM)) {
725 ep2 = rconst(ep2);
726 } else if (ep1->type == NUM && ep1->v.num == 0) {
727 epfree(ep3,1); /* (0 ^ E3) */
728 ep1->sibling = NULL;
729 efree(ep2);
730 ep2 = ep1;
731 } else if ((ep3->type == NUM && ep3->v.num == 0) |
732 (ep1->type == NUM && ep1->v.num == 1)) {
733 epfree(ep2,1); /* (E4 ^ 0) or (1 ^ E3) */
734 ep2 = newnode();
735 ep2->type = NUM;
736 ep2->v.num = 1;
737 } else if (ep3->type == NUM && ep3->v.num == 1) {
738 efree(ep3); /* (E4 ^ 1) */
739 ep1->sibling = NULL;
740 efree(ep2);
741 ep2 = ep1;
742 }
743 }
744 return(ep2);
745 }
746
747
748 EPNODE *
749 getE4(void) /* E4 -> ADDOP E5 */
750 /* E5 */
751 {
752 EPNODE *ep1, *ep2;
753
754 if (nextc == '-') {
755 scan();
756 ep2 = getE5();
757 if (ep2->type == NUM) {
758 ep2->v.num = -ep2->v.num;
759 return(ep2);
760 }
761 if (ep2->type == UMINUS) { /* don't generate -(-E5) */
762 ep1 = ep2->v.kid;
763 efree(ep2);
764 return(ep1);
765 }
766 ep1 = newnode();
767 ep1->type = UMINUS;
768 addekid(ep1, ep2);
769 return(ep1);
770 }
771 if (nextc == '+')
772 scan();
773 return(getE5());
774 }
775
776
777 EPNODE *
778 getE5(void) /* E5 -> (E1) */
779 /* VAR */
780 /* NUM */
781 /* $N */
782 /* FUNC(E1,..) */
783 /* ARG */
784 {
785 int i;
786 char *nam;
787 EPNODE *ep1, *ep2;
788
789 if (nextc == '(') {
790 scan();
791 ep1 = getE1();
792 if (nextc != ')')
793 syntax("')' expected");
794 scan();
795 return(ep1);
796 }
797 if (esupport&E_INCHAN && nextc == '$') {
798 scan();
799 ep1 = newnode();
800 ep1->type = CHAN;
801 ep1->v.chan = getinum();
802 return(ep1);
803 }
804 if (esupport&(E_VARIABLE|E_FUNCTION) &&
805 (isalpha(nextc) | (nextc == CNTXMARK))) {
806 nam = getname();
807 ep1 = NULL;
808 if ((esupport&(E_VARIABLE|E_FUNCTION)) == (E_VARIABLE|E_FUNCTION)
809 && curfunc != NULL)
810 for (i = 1, ep2 = curfunc->v.kid->sibling;
811 ep2 != NULL; i++, ep2 = ep2->sibling)
812 if (!strcmp(ep2->v.name, nam)) {
813 ep1 = newnode();
814 ep1->type = ARG;
815 ep1->v.chan = i;
816 break;
817 }
818 if (ep1 == NULL) {
819 ep1 = newnode();
820 ep1->type = VAR;
821 ep1->v.ln = varinsert(nam);
822 }
823 if (esupport&E_FUNCTION && nextc == '(') {
824 ep2 = newnode();
825 ep2->type = FUNC;
826 addekid(ep2, ep1);
827 ep1 = ep2;
828 do {
829 scan();
830 addekid(ep1, getE1());
831 } while (nextc == ',');
832 if (nextc != ')')
833 syntax("')' expected");
834 scan();
835 } else if (!(esupport&E_VARIABLE))
836 syntax("'(' expected");
837 if (esupport&E_RCONST && isconstvar(ep1))
838 ep1 = rconst(ep1);
839 return(ep1);
840 }
841 if (isdecimal(nextc)) {
842 ep1 = newnode();
843 ep1->type = NUM;
844 ep1->v.num = getnum();
845 return(ep1);
846 }
847 syntax("unexpected character");
848 return NULL; /* pro forma return */
849 }
850
851
852 EPNODE *
853 rconst( /* reduce a constant expression */
854 EPNODE *epar
855 )
856 {
857 EPNODE *ep;
858
859 ep = newnode();
860 ep->type = NUM;
861 errno = 0;
862 ep->v.num = evalue(epar);
863 if ((errno == EDOM) | (errno == ERANGE))
864 syntax("bad constant expression");
865 epfree(epar,1);
866
867 return(ep);
868 }
869
870
871 int
872 isconstvar( /* is ep linked to a constant expression? */
873 EPNODE *ep
874 )
875 {
876 EPNODE *ep1;
877
878 if (esupport&E_FUNCTION && ep->type == FUNC) {
879 if (!isconstfun(ep->v.kid))
880 return(0);
881 for (ep1 = ep->v.kid->sibling; ep1 != NULL; ep1 = ep1->sibling)
882 if (ep1->type != NUM && !isconstfun(ep1))
883 return(0);
884 return(1);
885 }
886 if (ep->type != VAR)
887 return(0);
888 ep1 = ep->v.ln->def;
889 if (ep1 == NULL || ep1->type != ':')
890 return(0);
891 if (esupport&E_FUNCTION && ep1->v.kid->type != SYM)
892 return(0);
893 return(1);
894 }
895
896
897 int
898 isconstfun( /* is ep linked to a constant function? */
899 EPNODE *ep
900 )
901 {
902 EPNODE *dp;
903 LIBR *lp;
904
905 if (ep->type != VAR)
906 return(0);
907 if ((dp = ep->v.ln->def) != NULL) {
908 if (dp->v.kid->type == FUNC)
909 return(dp->type == ':');
910 else
911 return(0); /* don't identify masked library functions */
912 }
913 if ((lp = ep->v.ln->lib) != NULL)
914 return(lp->atyp == ':');
915 return(0);
916 }