ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/calexpr.c
Revision: 2.48
Committed: Sun Feb 25 04:41:44 2024 UTC (3 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.47: +9 -12 lines
Log Message:
fix: Made error non-fatal in case optimization done twice

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: calexpr.c,v 2.47 2024/02/25 04:11:10 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 *), eargument(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 eargument(
270 EPNODE *ep
271 )
272 {
273 return(argument(ep->v.chan));
274 }
275
276 static double
277 enumber(
278 EPNODE *ep
279 )
280 {
281 return(ep->v.num);
282 }
283
284 static double
285 euminus(
286 EPNODE *ep
287 )
288 {
289 EPNODE *ep1 = ep->v.kid;
290
291 return(-evalue(ep1));
292 }
293
294 static double
295 echannel(
296 EPNODE *ep
297 )
298 {
299 return(chanvalue(ep->v.chan));
300 }
301
302 static double
303 eadd(
304 EPNODE *ep
305 )
306 {
307 double sum = 0;
308 EPNODE *ep1 = ep->v.kid;
309
310 do
311 sum += envalue(ep1);
312 while ((ep1 = ep1->sibling) != NULL);
313
314 return(sum);
315 }
316
317 static double
318 esubtr(
319 EPNODE *ep
320 )
321 {
322 EPNODE *ep1 = ep->v.kid;
323 EPNODE *ep2 = ep1->sibling;
324
325 return(envalue(ep1) - envalue(ep2));
326 }
327
328 static double
329 emult(
330 EPNODE *ep
331 )
332 {
333 double prod = 1;
334 EPNODE *ep1 = ep->v.kid;
335
336 do
337 prod *= envalue(ep1);
338 while ((ep1 = ep1->sibling) != NULL);
339
340 return(prod);
341 }
342
343 static double
344 edivi(
345 EPNODE *ep
346 )
347 {
348 EPNODE *ep1 = ep->v.kid;
349 double den = evalue(ep1->sibling);
350
351 if (den == 0.0) {
352 wputs("Division by zero\n");
353 errno = ERANGE;
354 return(0.0);
355 }
356 return(envalue(ep1) / den);
357 }
358
359 static double
360 epow(
361 EPNODE *ep
362 )
363 {
364 EPNODE *ep1 = ep->v.kid;
365 double d;
366 int lasterrno;
367
368 lasterrno = errno;
369 errno = 0;
370 d = pow(evalue(ep1), evalue(ep1->sibling));
371 #ifdef isnan
372 if (errno == 0) {
373 if (isnan(d))
374 errno = EDOM;
375 else if (isinf(d))
376 errno = ERANGE;
377 }
378 #endif
379 if ((errno == EDOM) | (errno == ERANGE)) {
380 wputs("Illegal power\n");
381 return(0.0);
382 }
383 errno = lasterrno;
384 return(d);
385 }
386
387 static double
388 ebotch(
389 EPNODE *ep
390 )
391 {
392 eputs("Bad expression!\n");
393 quit(1);
394 return 0.0; /* pro forma return */
395 }
396
397
398 EPNODE *
399 ekid( /* return pointer to a node's nth kid */
400 EPNODE *ep,
401 int n
402 )
403 {
404 if (ep->nkids < 0) { /* allocated array? */
405 if (n >= -ep->nkids)
406 return(NULL);
407 return(ep->v.kid + n);
408 }
409 ep = ep->v.kid; /* else get from list */
410 while (n-- > 0)
411 if ((ep = ep->sibling) == NULL)
412 break;
413 return(ep);
414 }
415
416
417 void
418 initfile( /* prepare input file */
419 FILE *fp,
420 char *fn,
421 int ln
422 )
423 {
424 static char inpbuf[MAXLINE];
425
426 infp = fp;
427 linbuf = inpbuf;
428 infile = fn;
429 lineno = ln;
430 linepos = 0;
431 inpbuf[0] = '\0';
432 scan();
433 }
434
435
436 void
437 initstr( /* prepare input string */
438 char *s,
439 char *fn,
440 int ln
441 )
442 {
443 infp = NULL;
444 infile = fn;
445 lineno = ln;
446 linbuf = s;
447 linepos = 0;
448 scan();
449 }
450
451
452 void
453 getscanpos( /* return current scan position */
454 char **fnp,
455 int *lnp,
456 char **spp,
457 FILE **fpp
458 )
459 {
460 if (fnp != NULL) *fnp = infile;
461 if (lnp != NULL) *lnp = lineno;
462 if (spp != NULL) *spp = linbuf+linepos;
463 if (fpp != NULL) *fpp = infp;
464 }
465
466
467 int
468 scan(void) /* scan next character, return literal next */
469 {
470 int lnext = 0;
471
472 do {
473 if (linbuf[linepos] == '\0')
474 if (infp == NULL || fgets(linbuf, MAXLINE, infp) == NULL)
475 nextc = EOF;
476 else {
477 nextc = linbuf[0];
478 lineno++;
479 linepos = 1;
480 }
481 else
482 nextc = linbuf[linepos++];
483 if (!lnext)
484 lnext = nextc;
485 if (nextc == eofc) {
486 nextc = EOF;
487 break;
488 }
489 if (nextc == '{') {
490 scan();
491 while (nextc != '}')
492 if (nextc == EOF)
493 syntax("'}' expected");
494 else
495 scan();
496 scan();
497 }
498 } while (isspace(nextc));
499 return(lnext);
500 }
501
502
503 char *
504 long2ascii( /* convert long to ascii */
505 long l
506 )
507 {
508 static char buf[16];
509 char *cp;
510 int neg = 0;
511
512 if (l == 0)
513 return("0");
514 if (l < 0) {
515 l = -l;
516 neg++;
517 }
518 cp = buf + sizeof(buf);
519 *--cp = '\0';
520 while (l) {
521 *--cp = l % 10 + '0';
522 l /= 10;
523 }
524 if (neg)
525 *--cp = '-';
526 return(cp);
527 }
528
529
530 void
531 syntax( /* report syntax error and quit */
532 char *err
533 )
534 {
535 int i;
536
537 if ((infile != NULL) | (lineno != 0)) {
538 if (infile != NULL) eputs(infile);
539 if (lineno != 0) {
540 eputs(infile != NULL ? ", line " : "line ");
541 eputs(long2ascii((long)lineno));
542 }
543 eputs(":\n");
544 }
545 eputs(linbuf);
546 if (linbuf[strlen(linbuf)-1] != '\n')
547 eputs("\n");
548 for (i = 0; i < linepos-1; i++)
549 eputs(linbuf[i] == '\t' ? "\t" : " ");
550 eputs("^ ");
551 eputs(err);
552 eputs("\n");
553 quit(1);
554 }
555
556
557 void
558 addekid( /* add a child to ep */
559 EPNODE *ep,
560 EPNODE *ek
561 )
562 {
563 if (ep->nkids < 0) {
564 eputs("Cannot add kid to EPNODE array\n");
565 quit(1);
566 }
567 ep->nkids++;
568 if (ep->v.kid == NULL)
569 ep->v.kid = ek;
570 else {
571 for (ep = ep->v.kid; ep->sibling != NULL; ep = ep->sibling)
572 ;
573 ep->sibling = ek;
574 }
575 ek->sibling = NULL; /* shouldn't be necessary */
576 }
577
578
579 char *
580 getname(void) /* scan an identifier */
581 {
582 static char str[RMAXWORD+1];
583 int i, lnext;
584
585 lnext = nextc;
586 for (i = 0; i < RMAXWORD && isid(lnext); i++, lnext = scan())
587 str[i] = lnext;
588 str[i] = '\0';
589 while (isid(lnext)) /* skip rest of name */
590 lnext = scan();
591
592 return(str);
593 }
594
595
596 int
597 getinum(void) /* scan a positive integer */
598 {
599 int n, lnext;
600
601 n = 0;
602 lnext = nextc;
603 while (isdigit(lnext)) {
604 n = n * 10 + lnext - '0';
605 lnext = scan();
606 }
607 return(n);
608 }
609
610
611 double
612 getnum(void) /* scan a positive float */
613 {
614 int i, lnext;
615 char str[RMAXWORD+1];
616
617 i = 0;
618 lnext = nextc;
619 while (isdigit(lnext) && i < RMAXWORD) {
620 str[i++] = lnext;
621 lnext = scan();
622 }
623 if ((lnext == '.') & (i < RMAXWORD)) {
624 str[i++] = lnext;
625 lnext = scan();
626 if (i == 1 && !isdigit(lnext))
627 syntax("badly formed number");
628 while (isdigit(lnext) && i < RMAXWORD) {
629 str[i++] = lnext;
630 lnext = scan();
631 }
632 }
633 if ((lnext == 'e') | (lnext == 'E') && i < RMAXWORD) {
634 str[i++] = lnext;
635 lnext = scan();
636 if ((lnext == '-') | (lnext == '+') && i < RMAXWORD) {
637 str[i++] = lnext;
638 lnext = scan();
639 }
640 if (!isdigit(lnext))
641 syntax("missing exponent");
642 while (isdigit(lnext) && i < RMAXWORD) {
643 str[i++] = lnext;
644 lnext = scan();
645 }
646 }
647 str[i] = '\0';
648
649 return(atof(str));
650 }
651
652
653 EPNODE *
654 getE1(void) /* E1 -> E1 ADDOP E2 */
655 /* E2 */
656 {
657 EPNODE *ep1, *ep2;
658
659 ep1 = getE2();
660 while ((nextc == '+') | (nextc == '-')) {
661 ep2 = newnode();
662 ep2->type = nextc;
663 scan();
664 addekid(ep2, ep1);
665 addekid(ep2, getE2());
666 if (esupport&E_RCONST &&
667 (ep1->type == NUM) & (ep1->sibling->type == NUM))
668 ep2 = rconst(ep2);
669 ep1 = ep2;
670 }
671 return(ep1);
672 }
673
674
675 EPNODE *
676 getE2(void) /* E2 -> E2 MULOP E3 */
677 /* E3 */
678 {
679 EPNODE *ep1, *ep2;
680
681 ep1 = getE3();
682 while ((nextc == '*') | (nextc == '/')) {
683 ep2 = newnode();
684 ep2->type = nextc;
685 scan();
686 addekid(ep2, ep1);
687 addekid(ep2, getE3());
688 if (esupport&E_RCONST) {
689 EPNODE *ep3 = ep1->sibling;
690 if ((ep1->type == NUM) & (ep3->type == NUM)) {
691 ep2 = rconst(ep2);
692 } else if (ep3->type == NUM) {
693 if (ep2->type == '/') {
694 if (ep3->v.num == 0)
695 syntax("divide by zero constant");
696 ep2->type = '*'; /* for speed */
697 ep3->v.num = 1./ep3->v.num;
698 } else if (ep3->v.num == 0) {
699 ep1->sibling = NULL; /* (E2 * 0) */
700 epfree(ep2,1);
701 ep2 = ep3;
702 }
703 } else if (ep1->type == NUM && ep1->v.num == 0) {
704 epfree(ep3,1); /* (0 * E3) or (0 / E3) */
705 ep1->sibling = NULL;
706 efree(ep2);
707 ep2 = ep1;
708 }
709 }
710 ep1 = ep2;
711 }
712 return(ep1);
713 }
714
715
716 EPNODE *
717 getE3(void) /* E3 -> E4 ^ E3 */
718 /* E4 */
719 {
720 EPNODE *ep1, *ep2;
721
722 ep1 = getE4();
723 if (nextc != '^')
724 return(ep1);
725 ep2 = newnode();
726 ep2->type = nextc;
727 scan();
728 addekid(ep2, ep1);
729 addekid(ep2, getE3());
730 if (esupport&E_RCONST) {
731 EPNODE *ep3 = ep1->sibling;
732 if ((ep1->type == NUM) & (ep3->type == NUM)) {
733 ep2 = rconst(ep2);
734 } else if (ep1->type == NUM && ep1->v.num == 0) {
735 epfree(ep3,1); /* (0 ^ E3) */
736 ep1->sibling = NULL;
737 efree(ep2);
738 ep2 = ep1;
739 } else if ((ep3->type == NUM && ep3->v.num == 0) |
740 (ep1->type == NUM && ep1->v.num == 1)) {
741 epfree(ep2,1); /* (E4 ^ 0) or (1 ^ E3) */
742 ep2 = newnode();
743 ep2->type = NUM;
744 ep2->v.num = 1;
745 } else if (ep3->type == NUM && ep3->v.num == 1) {
746 efree(ep3); /* (E4 ^ 1) */
747 ep1->sibling = NULL;
748 efree(ep2);
749 ep2 = ep1;
750 }
751 }
752 return(ep2);
753 }
754
755
756 EPNODE *
757 getE4(void) /* E4 -> ADDOP E5 */
758 /* E5 */
759 {
760 EPNODE *ep1, *ep2;
761
762 if (nextc == '-') {
763 scan();
764 ep2 = getE5();
765 if (ep2->type == NUM) {
766 ep2->v.num = -ep2->v.num;
767 return(ep2);
768 }
769 if (ep2->type == UMINUS) { /* don't generate -(-E5) */
770 ep1 = ep2->v.kid;
771 efree(ep2);
772 return(ep1);
773 }
774 ep1 = newnode();
775 ep1->type = UMINUS;
776 addekid(ep1, ep2);
777 return(ep1);
778 }
779 if (nextc == '+')
780 scan();
781 return(getE5());
782 }
783
784
785 EPNODE *
786 getE5(void) /* E5 -> (E1) */
787 /* VAR */
788 /* NUM */
789 /* $N */
790 /* FUNC(E1,..) */
791 /* ARG */
792 {
793 int i;
794 char *nam;
795 EPNODE *ep1, *ep2;
796
797 if (nextc == '(') {
798 scan();
799 ep1 = getE1();
800 if (nextc != ')')
801 syntax("')' expected");
802 scan();
803 return(ep1);
804 }
805 if (esupport&E_INCHAN && nextc == '$') {
806 scan();
807 ep1 = newnode();
808 ep1->type = CHAN;
809 ep1->v.chan = getinum();
810 return(ep1);
811 }
812 if (esupport&(E_VARIABLE|E_FUNCTION) &&
813 (isalpha(nextc) | (nextc == CNTXMARK))) {
814 nam = getname();
815 ep1 = NULL;
816 if ((esupport&(E_VARIABLE|E_FUNCTION)) == (E_VARIABLE|E_FUNCTION)
817 && curfunc != NULL)
818 for (i = 1, ep2 = curfunc->v.kid->sibling;
819 ep2 != NULL; i++, ep2 = ep2->sibling)
820 if (!strcmp(ep2->v.name, nam)) {
821 ep1 = newnode();
822 ep1->type = ARG;
823 ep1->v.chan = i;
824 break;
825 }
826 if (ep1 == NULL) {
827 ep1 = newnode();
828 ep1->type = VAR;
829 ep1->v.ln = varinsert(nam);
830 }
831 if (esupport&E_FUNCTION && nextc == '(') {
832 ep2 = newnode();
833 ep2->type = FUNC;
834 addekid(ep2, ep1);
835 ep1 = ep2;
836 do {
837 scan();
838 addekid(ep1, getE1());
839 } while (nextc == ',');
840 if (nextc != ')')
841 syntax("')' expected");
842 scan();
843 } else if (!(esupport&E_VARIABLE))
844 syntax("'(' expected");
845 if (esupport&E_RCONST && isconstvar(ep1))
846 ep1 = rconst(ep1);
847 return(ep1);
848 }
849 if (isdecimal(nextc)) {
850 ep1 = newnode();
851 ep1->type = NUM;
852 ep1->v.num = getnum();
853 return(ep1);
854 }
855 syntax("unexpected character");
856 return NULL; /* pro forma return */
857 }
858
859
860 EPNODE *
861 rconst( /* reduce a constant expression */
862 EPNODE *epar
863 )
864 {
865 EPNODE *ep;
866
867 ep = newnode();
868 ep->type = NUM;
869 errno = 0;
870 ep->v.num = evalue(epar);
871 if ((errno == EDOM) | (errno == ERANGE))
872 syntax("bad constant expression");
873 epfree(epar,1);
874
875 return(ep);
876 }
877
878
879 int
880 isconstvar( /* is ep linked to a constant expression? */
881 EPNODE *ep
882 )
883 {
884 EPNODE *ep1;
885
886 if (esupport&E_FUNCTION && ep->type == FUNC) {
887 if (!isconstfun(ep->v.kid))
888 return(0);
889 for (ep1 = ep->v.kid->sibling; ep1 != NULL; ep1 = ep1->sibling)
890 if (ep1->type != NUM && !isconstfun(ep1))
891 return(0);
892 return(1);
893 }
894 if (ep->type != VAR)
895 return(0);
896 ep1 = ep->v.ln->def;
897 if (ep1 == NULL || ep1->type != ':')
898 return(0);
899 if (esupport&E_FUNCTION && ep1->v.kid->type != SYM)
900 return(0);
901 return(1);
902 }
903
904
905 int
906 isconstfun( /* is ep linked to a constant function? */
907 EPNODE *ep
908 )
909 {
910 EPNODE *dp;
911 LIBR *lp;
912
913 if (ep->type != VAR)
914 return(0);
915 if ((dp = ep->v.ln->def) != NULL) {
916 if (dp->v.kid->type == FUNC)
917 return(dp->type == ':');
918 else
919 return(0); /* don't identify masked library functions */
920 }
921 if ((lp = ep->v.ln->lib) != NULL)
922 return(lp->atyp == ':');
923 return(0);
924 }