1 /***********************************************************************
2 * show.c - display expressions and their values
3 ***********************************************************************
4 *
5 * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include <math.h>
23 #include <ctype.h>
24 #include "show.h"
25 #include "impl.h"
26 #include "dstruct.h"
27 #include "lexicon.h"
28 #include "pragmatics.h"
29 #if defined(HAVE_IEEEFP_H)
30 #include <ieeefp.h>
31 #endif
32
33 /***********************************************************************
34 * local declarations
35 ***********************************************************************/
36
37 static struct {
38 int op;
39 char *str;
40 } opstr[] = {
41 { RULE, "->" },
42 { CND_FETCH, "<fetch node>" },
43 { CND_DELAY, "<delay node>" },
44 { CND_RATE, "rate" },
45 { CND_NEG, "-" },
46 { CND_ADD, "+" },
47 { CND_SUB, "-" },
48 { CND_MUL, "*" },
49 { CND_DIV, "/" },
50 /* aggregation */
51 { CND_SUM_HOST, "sum_host" },
52 { CND_SUM_INST, "sum_inst" },
53 { CND_SUM_TIME, "sum_sample" },
54 { CND_AVG_HOST, "avg_host" },
55 { CND_AVG_INST, "avg_inst" },
56 { CND_AVG_TIME, "avg_sample" },
57 { CND_MAX_HOST, "max_host" },
58 { CND_MAX_INST, "max_inst" },
59 { CND_MAX_TIME, "max_sample" },
60 { CND_MIN_HOST, "min_host" },
61 { CND_MIN_INST, "min_inst" },
62 { CND_MIN_TIME, "min_sample" },
63 /* relational */
64 { CND_EQ, "==" },
65 { CND_NEQ, "!=" },
66 { CND_LT, "<" },
67 { CND_LTE, "<=" },
68 { CND_GT, ">" },
69 { CND_GTE, ">=" },
70 /* boolean */
71 { CND_NOT, "!" },
72 { CND_RISE, "rising" },
73 { CND_FALL, "falling" },
74 { CND_AND, "&&" },
75 { CND_OR, "||" },
76 { CND_MATCH, "match_inst" },
77 { CND_NOMATCH, "nomatch_inst" },
78 /* quantification */
79 { CND_ALL_HOST, "all_host" },
80 { CND_ALL_INST, "all_inst" },
81 { CND_ALL_TIME, "all_sample" },
82 { CND_SOME_HOST, "some_host" },
83 { CND_SOME_INST, "some_inst" },
84 { CND_SOME_TIME, "some_sample" },
85 { CND_PCNT_HOST, "pcnt_host" },
86 { CND_PCNT_INST, "pcnt_inst" },
87 { CND_PCNT_TIME, "pcnt_sample" },
88 { CND_COUNT_HOST, "count_host" },
89 { CND_COUNT_INST, "count_inst" },
90 { CND_COUNT_TIME, "count_sample" },
91 { ACT_SEQ, "&" },
92 { ACT_ALT, "|" },
93 { ACT_SHELL, "shell" },
94 { ACT_ALARM, "alarm" },
95 { ACT_SYSLOG, "syslog" },
96 { ACT_PRINT, "print" },
97 { ACT_STOMP, "stomp" },
98 { ACT_ARG, "<action arg node>" },
99 { NOP, "<nop node>" },
100 { OP_VAR, "<op_var node>" },
101 };
102
103 static int numopstr = sizeof(opstr) / sizeof(opstr[0]);
104
105 /***********************************************************************
106 * local utility functions
107 ***********************************************************************/
108
109 /* Concatenate string1 to existing string2 whose original length is given. */
110 static size_t /* new length of *string2 */
111 concat(char *string1, size_t pos, char **string2)
112 {
113 size_t slen;
114 size_t tlen;
115 char *cat;
116 char *dog;
117
118 if ((slen = strlen(string1)) == 0)
119 return pos;
120 tlen = pos + slen;
121 cat = (char *) ralloc(*string2, tlen + 1);
122 dog = cat + pos;
123 strcpy(dog, string1);
124 dog += slen;
125 *dog = '\0';
126
127 *string2 = cat;
128 return tlen;
129 }
130
131
132 /***********************************************************************
133 * host and instance names
134 ***********************************************************************/
135
136 /* Return host and instance name for nth value in expression *x */
137 static int
138 lookupHostInst(Expr *x, int nth, char **host, char **inst)
139 {
140 Metric *m;
141 int mi;
142 int sts = 0;
143 #if PCP_DEBUG
144 static Expr *lastx = NULL;
145 int dbg_dump = 0;
146 #endif
147
148 #if PCP_DEBUG
149 if (pmDebug & DBG_TRACE_APPL2) {
150 if (x != lastx) {
151 fprintf(stderr, "lookupHostInst(x=" PRINTF_P_PFX "%p, nth=%d, ...)\n", x, nth);
152 lastx = x;
153 dbg_dump = 1;
154 }
155 }
156 #endif
157
158 /* check for no host and instance available e.g. constant expression */
159 if ((x->e_idom <= 0 && x->hdom <= 0) || ! x->metrics) {
160 *host = NULL;
161 *inst = NULL;
162 #if PCP_DEBUG
163 if (pmDebug & DBG_TRACE_APPL2) {
164 fprintf(stderr, "lookupHostInst(x=" PRINTF_P_PFX "%p, nth=%d, ...) -> %%h and %%i undefined\n", x, nth);
165 }
166 #endif
167 return sts;
168 }
169
170 /* find Metric containing the nth instance */
171 mi = 0;
172 for (;;) {
173 m = &x->metrics[mi];
174 #if PCP_DEBUG
175 if ((pmDebug & DBG_TRACE_APPL2) && dbg_dump) {
176 fprintf(stderr, "lookupHostInst: metrics[%d]\n", mi);
177 dumpMetric(m);
178 }
179 #endif
180 if (nth < m->m_idom)
181 break;
182 if (m->m_idom > 0)
183 nth -= m->m_idom;
184 mi++;
185 }
186
187 /* host and instance names */
188 *host = symName(m->hname);
189 sts++;
190 if (x->e_idom > 0 && m->inames) {
191 *inst = m->inames[nth];
192 sts++;
193 }
194 else
195 *inst = NULL;
196
197 #if PCP_DEBUG
198 if (pmDebug & DBG_TRACE_APPL2) {
199 fprintf(stderr, "lookupHostInst(x=" PRINTF_P_PFX "%p, nth=%d, ...) -> sts=%d %%h=%s %%i=%s\n",
200 x, nth, sts, *host, *inst == NULL ? "undefined" : *inst);
201 }
202 #endif
203
204 return sts;
205 }
206
207
208 /***********************************************************************
209 * expression value
210 ***********************************************************************/
211
212 #define TRUTH_SPACE 7
213
214 static size_t
215 showTruth(Expr *x, int nth, size_t length, char **string)
216 {
217 int smpl;
218 size_t tlen;
219 char *cat;
220 char *dog;
221 int val;
222
223 tlen = length + (x->nsmpls * TRUTH_SPACE);
224 cat = (char *)ralloc(*string, tlen + 1);
225 dog = cat + length;
226 for (smpl = 0; smpl < x->nsmpls; smpl++) {
227 if (smpl > 0) {
228 strcpy(dog, " ");
229 dog += 1;
230 }
231
232 if (x->valid == 0) {
233 strncpy(dog, "?", TRUTH_SPACE);
234 dog++;
235 continue;
236 }
237
238 val = *((char *)x->smpls[smpl].ptr + nth);
239 if (val == FALSE) {
240 strncpy(dog, "false", TRUTH_SPACE);
241 dog += 5;
242 }
243 else if (val == TRUE) {
244 strncpy(dog, "true", TRUTH_SPACE);
245 dog += 4;
246 }
247 else if (val == DUNNO) {
248 strncpy(dog, "?", TRUTH_SPACE);
249 dog++;
250 }
251 else {
252 sprintf(dog, "0x%02x?", val & 0xff);
253 dog += 5;
254 }
255 }
256 *dog = '\0';
257
258 *string = cat;
259 return dog - cat;
260 }
261
262
263 static size_t
264 showString(Expr *x, size_t length, char **string)
265 {
266 size_t tlen;
267 char *cat;
268 char *dog;
269
270 tlen = length + (x->nvals - 1) + 2;
271 cat = (char *)ralloc(*string, tlen + 1);
272 dog = cat + length;
273 *dog++ = '"';
274 strcpy(dog, (char *)x->smpls[0].ptr);
275 dog += x->nvals - 1;
276 *dog++ = '"';
277 *dog = '\0';
278
279 *string = cat;
280 return tlen;
281 }
282
283 #define DBL_SPACE 24
284
285 static size_t
286 showNum(Expr *x, int nth, size_t length, char **string)
287 {
288 int smpl;
289 size_t tlen;
290 char *cat;
291 char *dog;
292 char *fmt;
293 double v;
294 double abs_v;
295 int sts;
296
297 tlen = length + (x->nsmpls * DBL_SPACE);
298 cat = (char *)ralloc(*string, tlen + 1);
299 dog = cat + length;
300 for (smpl = 0; smpl < x->nsmpls; smpl++) {
301 if (smpl > 0) {
302 strcpy(dog, " ");
303 dog += 1;
304 }
305 if (x->valid <= smpl || isnand(*((double *)x->smpls[smpl].ptr + nth))) {
306 strcpy(dog, "?");
307 dog += 1;
308 }
309 else {
310 v = *((double *)x->smpls[smpl].ptr+nth);
311 if (v == (int)v)
312 sts = sprintf(dog, "%d", (int)v);
313 else {
314 abs_v = v < 0 ? -v : v;
315 if (abs_v < 0.5)
316 fmt = "%g";
317 else if (abs_v < 5)
318 fmt = "%.2f";
319 else if (abs_v < 50)
320 fmt = "%.1f";
321 else
322 fmt = "%.0f";
323 sts = sprintf(dog, fmt, v);
324 }
325 if (sts > 0)
326 dog += sts;
327 else {
328 strcpy(dog, "!");
329 dog += 1;
330 }
331 }
332 }
333 *dog = '\0';
334
335 *string = cat;
336 return dog - cat;
337 }
338
339 static char *
340 showConst(Expr *x)
341 {
342 char *string = NULL;
343 size_t length = 0;
344 int i;
345 int first = 1;
346
347 /* construct string representation */
348 if (x->nvals > 0) {
349 for (i = 0; i < x->tspan; i++) {
350 if (first)
351 first = 0;
352 else
353 length = concat(" ", length, &string);
354 if (x->sem == SEM_TRUTH)
355 length = showTruth(x, i, length, &string);
356 else if (x->sem == SEM_CHAR)
357 length = showString(x, length, &string);
358 else
359 length = showNum(x, i, length, &string);
360 }
361 }
362 return string;
363 }
364
365
366
367 /***********************************************************************
368 * expression syntax
369 ***********************************************************************/
370
371 static void
372 showSyn(FILE *f, Expr *x)
373 {
374 char *s;
375 char *c;
376 Metric *m;
377 char **n;
378 int i;
379
380 /* constant */
381 if (x->op >= NOP) {
382 s = showConst(x);
383 if (s) {
384 c = s;
385 while(isspace((int)*c))
386 c++;
387 fputs(c, f);
388 free(s);
389 }
390 }
391
392 /* fetch expression (perhaps with delay) */
393 else if ((x->op == CND_FETCH) || (x->op == CND_DELAY)) {
394 m = x->metrics;
395 fprintf(f, "%s", symName(m->mname));
396 for (i = 0; i < x->hdom; i++) {
397 fprintf(f, " :%s", symName(m->hname));
398 m++;
399 }
400 m = x->metrics;
401 if (m->inames) {
402 n = m->inames;
403 for (i = 0; i < m->m_idom; i++) {
404 fprintf(f, " #%s", *n);
405 n++;
406 }
407 }
408 if (x->op == CND_FETCH) {
409 if (x->tdom == 1) fprintf(f, " @0");
410 else fprintf(f, " @0..%d", x->tdom - 1);
411 }
412 else {
413 if (x->tdom == x->arg1->tdom - 1) fprintf(f, " @%d", x->tdom);
414 else fprintf(f, " @%d..%d", x->tdom, x->tdom + x->arg1->tdom - 1);
415 }
416 }
417
418 /* binary operator */
|
Event var_compare_op: |
Comparing "x->arg1" to null implies that "x->arg1" might be null. |
| Also see events: |
[var_deref_op] |
|
At conditional (1): "x->arg1": Taking false branch.
|
419 else if (x->arg1 && x->arg2) {
420 if (x->op >= ACT_SHELL && x->op < ACT_ARG) {
421 fputs(opStrings(x->op), f);
422 fputc(' ', f);
423 showSyn(f, x->arg2);
424 fputc(' ', f);
425 showSyn(f, x->arg1);
426 }
427 else if (x->op >= CND_PCNT_HOST && x->op <= CND_PCNT_TIME) {
428 showSyn(f, x->arg2);
429 fputc(' ', f);
430 fputs(opStrings(x->op), f);
431 fputc(' ', f);
432 if (x->arg1->op >= NOP || x->arg1->op <= CND_DELAY)
433 showSyn(f, x->arg1);
434 else {
435 fputc('(', f);
436 showSyn(f, x->arg1);
437 fputc(')', f);
438 }
439 }
440 else {
441 if (x->arg1->op >= NOP || x->arg1->op <= CND_DELAY)
442 showSyn(f, x->arg1);
443 else {
444 fputc('(', f);
445 showSyn(f, x->arg1);
446 fputc(')', f);
447 }
448 fputc(' ', f);
449 fputs(opStrings(x->op), f);
450 fputc(' ', f);
451 if (x->arg2->op >= NOP || x->arg2->op <= CND_DELAY)
452 showSyn(f, x->arg2);
453 else {
454 fputc('(', f);
455 showSyn(f, x->arg2);
456 fputc(')', f);
457 }
458 }
459 }
460
461 /* unary operator */
462 else {
463 fputs(opStrings(x->op), f);
464 fputc(' ', f);
|
Event var_deref_op: |
Dereferencing null variable "x->arg1". |
| Also see events: |
[var_compare_op] |
465 if (x->arg1->op >= ACT_ARG || x->arg1->op <= CND_DELAY)
466 showSyn(f, x->arg1);
467 else {
468 fputc('(', f);
469 showSyn(f, x->arg1);
470 fputc(')', f);
471 }
472 }
473 }
474
475 /*
476 * recursive descent to find a conjunct from the root of
477 * the expression that has associated metrics (not constants)
478 */
479 static Expr *
480 findMetrics(Expr *y)
481 {
482 Expr *z;
483
484 if (y == NULL) return NULL;
485 if (y->metrics) return y; /* success */
486
487 /* give up if not a conjunct */
488 if (y->op != CND_AND) return NULL;
489
490 /* recurse left and then right */
491 z = findMetrics(y->arg1);
492 if (z != NULL) return z;
493 return findMetrics(y->arg2);
494 }
495
496 /***********************************************************************
497 * satisfying bindings and values
498 ***********************************************************************/
499
500 /* Find sub-expression that reveals host and instance bindings
501 that satisfy the given expression *x. */
502 static Expr *
503 findBindings(Expr *x)
504 {
505 #if PCP_DEBUG
506 if (pmDebug & DBG_TRACE_APPL2) {
507 fprintf(stderr, "call findBindings(x=" PRINTF_P_PFX "%p)\n", x);
508 }
509 #endif
510
511 if (x->metrics == NULL) {
512 /*
513 * this Expr node has no metrics (involves only constants)
514 * ... try and find a conjunct at the top level that has
515 * associated metrics
516 */
517 Expr *y = findMetrics(x->arg1);
518 if (y != NULL) x = y;
519 }
520 while (x->metrics && (x->e_idom <= 0 || x->hdom <= 0)) {
521 if (x->op == CND_SUM_HOST || x->op == CND_SUM_INST || x->op == CND_SUM_TIME ||
522 x->op == CND_AVG_HOST || x->op == CND_AVG_INST || x->op == CND_AVG_TIME ||
523 x->op == CND_MAX_HOST || x->op == CND_MAX_INST || x->op == CND_MAX_TIME ||
524 x->op == CND_MIN_HOST || x->op == CND_MIN_INST || x->op == CND_MIN_TIME)
525 /*
526 * don't descend below an aggregation operator with a singular
527 * value, ... value you seek is right here
528 */
529 break;
530 if (x->arg1 && x->metrics == x->arg1->metrics)
531 x = x->arg1;
532 else if (x->arg2)
533 x = x->arg2;
534 else
535 break;
536 #if PCP_DEBUG
537 if (pmDebug & DBG_TRACE_APPL2) {
538 fprintf(stderr, "findBindings: try x=" PRINTF_P_PFX "%p\n", x);
539 }
540 #endif
541 }
542 return x;
543 }
544
545
546 /* Find sub-expression that reveals the values that satisfy the
547 given expression *x. */
548 static Expr *
549 findValues(Expr *x)
550 {
551 #if PCP_DEBUG
552 if (pmDebug & DBG_TRACE_APPL2) {
553 fprintf(stderr, "call findValues(x=" PRINTF_P_PFX "%p)\n", x);
554 }
555 #endif
556 while (x->sem == SEM_TRUTH && x->metrics) {
557 if (x->metrics == x->arg1->metrics)
558 x = x->arg1;
559 else
560 x = x->arg2;
561 #if PCP_DEBUG
562 if (pmDebug & DBG_TRACE_APPL2) {
563 fprintf(stderr, "findValues: try x=" PRINTF_P_PFX "%p\n", x);
564 }
565 #endif
566 }
567 return x;
568 }
569
570
571 /***********************************************************************
572 * format string
573 ***********************************************************************/
574
575 /* Locate next %h, %i or %v in format string. */
576 static int /* 0 -> not found, 1 -> host, 2 -> inst, 3 -> value */
577 findFormat(char *format, char **pos)
578 {
579 for (;;) {
580 if (*format == '\0')
581 return 0;
582 if (*format == '%') {
583 switch (*(format + 1)) {
584 case 'h':
585 *pos = format;
586 return 1;
587 case 'i':
588 *pos = format;
589 return 2;
590 case 'v':
591 *pos = format;
592 return 3;
593 }
594 }
595 format++;
596 }
597
598 }
599
600
601 /***********************************************************************
602 * exported functions
603 ***********************************************************************/
604
605 void
606 showSyntax(FILE *f, Symbol s)
607 {
608 char *name = symName(s);
609 Expr *x = symValue(s);
610
611 fprintf(f, "%s =\n", name);
612 showSyn(f, x);
613 fprintf(f, ";\n\n");
614 }
615
616
617 void
618 showSubsyntax(FILE *f, Symbol s)
619 {
620 char *name = symName(s);
621 Expr *x = symValue(s);
622 Expr *x1;
623 Expr *x2;
624
625 fprintf(f, "%s (subexpression for %s, %s and %s bindings) =\n",
626 name, "%h", "%i", "%v");
627 x1 = findBindings(x);
628 x2 = findValues(x1);
629 showSyn(f, x2);
630 fprintf(f, "\n\n");
631 }
632
633
634 /* Print value of expression */
635 void
636 showValue(FILE *f, Expr *x)
637 {
638 char *string = NULL;
639
640 string = showConst(x);
641 if (string) {
642 fputs(string, f);
643 free(string);
644 }
645 else
646 fputs("?", f);
647 }
648
649
650 /* Print value of expression together with any host and instance bindings */
651 void
652 showAnnotatedValue(FILE *f, Expr *x)
653 {
654 char *string = NULL;
655 size_t length = 0;
656 char *host;
657 char *inst;
658 int i;
659
660 /* no annotation possible */
661 if ((x->e_idom <= 0 && x->hdom <= 0) ||
662 x->sem == SEM_CHAR ||
663 x->metrics == NULL ||
664 x->valid == 0) {
665 showValue(f, x);
666 return;
667 }
668
669 /* construct string representation */
670 for (i = 0; i < x->tspan; i++) {
671 length = concat("\n ", length, &string);
672 lookupHostInst(x, i, &host, &inst);
673 length = concat(host, length, &string);
674 if (inst) {
675 length = concat(": [", length, &string);
676 length = concat(inst, length, &string);
677 length = concat("] ", length, &string);
678 }
679 else
680 length = concat(": ", length, &string);
681 if (x->sem == SEM_TRUTH)
682 length = showTruth(x, i, length, &string);
683 else /* numeric value */
684 length = showNum(x, i, length, &string);
685 }
686
687 /* print string representation */
688 if (string) {
689 fputs(string, f);
690 free(string);
691 }
692 }
693
694
695 void
696 showTime(FILE *f, RealTime rt)
697 {
698 time_t t = (time_t)rt;
699 char bfr[26];
700
701 pmCtime(&t, bfr);
702 bfr[24] = '\0';
703 fprintf(f, "%s", bfr);
704 }
705
706
707 void
708 showFullTime(FILE *f, RealTime rt)
709 {
710 time_t t = (time_t)rt;
711 char bfr[26];
712
713 pmCtime(&t, bfr);
714 bfr[24] = '\0';
715 fprintf(f, "%s.%06d", bfr, (int)((rt-t)*1000000));
716 }
717
718
719 void
720 showSatisfyingValue(FILE *f, Expr *x)
721 {
722 char *string = NULL;
723 size_t length = 0;
724 char *host;
725 char *inst;
726 int i;
727 Expr *x1;
728 Expr *x2;
729
730 /* no satisfying values possible */
731 if (x->metrics == NULL || x->valid == 0) {
732 showValue(f, x);
733 return;
734 }
735
736 if (x->sem != SEM_TRUTH) {
737 showAnnotatedValue(f, x);
738 return;
739 }
740
741 x1 = findBindings(x);
742 x2 = findValues(x1);
743
744 /* construct string representation */
745 for (i = 0; i < x1->tspan; i++) {
746 if (!x1->valid) continue;
747 if ((x1->sem == SEM_TRUTH && *((char *)x1->smpls[0].ptr + i) == TRUE)
748 || (x1->sem != SEM_TRUTH && x1->sem != SEM_UNKNOWN)) {
749 length = concat("\n ", length, &string);
750 lookupHostInst(x1, i, &host, &inst);
751 length = concat(host, length, &string);
752 if (inst) {
753 length = concat(": [", length, &string);
754 length = concat(inst, length, &string);
755 length = concat("] ", length, &string);
756 }
757 else
758 length = concat(": ", length, &string);
759 if (x2->sem == SEM_TRUTH)
760 length = showTruth(x2, i, length, &string);
761 else /* numeric value */
762 length = showNum(x2, i, length, &string);
763 }
764 }
765
766 /* print string representation */
767 if (string) {
768 fputs(string, f);
769 free(string);
770 }
771 }
772
773
774 /* Instantiate format string for each satisfying binding and value
775 of the current rule.
776 WARNING: This is not thread safe, it dinks with the format string. */
777 size_t /* new length of string */
778 formatSatisfyingValue(char *format, size_t length, char **string)
779 {
780 char *host;
781 char *inst;
782 char *first;
783 char *prev;
784 char *next;
785 int i;
786 Expr *x1;
787 Expr *x2;
788 int sts1;
789 int sts2;
790
791 /* no formatting present? */
792 if ((sts1 = findFormat(format, &first)) == 0)
793 return concat(format, length, string);
794
795 x1 = findBindings(curr);
796 x2 = findValues(x1);
797
798 for (i = 0; i < x1->tspan; i++) {
799 if (!x1->valid) continue;
800 if ((x1->sem == SEM_TRUTH && *((char *)x1->smpls[0].ptr + i) == TRUE)
801 || (x1->sem != SEM_TRUTH && x1->sem != SEM_UNKNOWN)) {
802 prev = format;
803 next = first;
804 sts2 = sts1;
805 lookupHostInst(x1, i, &host, &inst);
806 do {
807 *next = '\0';
808 length = concat(prev, length, string);
809 *next = '%';
810
811 switch (sts2) {
812 case 1:
813 if (host)
814 length = concat(host, length, string);
815 else
816 length = concat("??? unknown %h", length, string);
817 break;
818 case 2:
819 if (inst)
820 length = concat(inst, length, string);
821 else
822 length = concat("??? unknown %i", length, string);
823 break;
824 case 3:
825 if (x2->sem == SEM_TRUTH)
826 length = showTruth(x2, i, length, string);
827 else /* numeric value */
828 length = showNum(x2, i, length, string);
829 break;
830 }
831 prev = next + 2;
832 } while ((sts2 = findFormat(prev, &next)));
833 length = concat(prev, length, string);
834 }
835 }
836
837 return length;
838 }
839
840 char *
841 opStrings(int op)
842 {
843 int i;
844 /*
845 * sizing of "eh" is a bit tricky ...
846 * XXXXXXXXX is the number of digits in the largest possible value
847 * for "op", to handle the default "<unknown op %d>" case, but also
848 * "eh" must be long enough to accommodate the longest string from
849 * opstr[i].str ... currently "<action arg node>"
850 */
851 static char *eh = "<unknown op XXXXXXXXX>";
852
853 for (i = 0; i < numopstr; i++) {
854 if (opstr[i].op == op)
855 break;
856 }
857
858 if (i < numopstr)
859 return opstr[i].str;
860 else {
861 sprintf(eh, "<unknown op %d>", op);
862 return eh;
863 }
864 }