1 #if 0
2 \documentclass{article}
3 \usepackage{axiom}
4 \begin{document}
5 \title{\$SPAD/etc asq.c}
6 \author{Timothy Daly and Waldek Hebisch}
7 \maketitle
8 \begin{abstract}
9 [[asq]] is a mini-browser for FriCAS databases.  It understands
10 structure of databases and can retrieve information about
11 constructors.  To do this it implements (limited) Lisp-like
12 S-expressions.
13 \end{abstract}
14 \eject
15 \tableofcontents
16 \eject
17 \section{S-expressions}
18 
19 Data in FriCAS databases is stored as S-expressions.  One could
20 try to handle them with purely string based methods.  But such
21 approach would be inflexible and awkward. Namely, FriCAS perform
22 various transformations before writing S-expressions to files.
23 Such transformations are natural and easy on S-expressions,
24 but complicated (require parsing and multiple string substitutions)
25 on string level.  So [[asq]]
26 needs S-expression support.  FriCAS uses only very special forms
27 of S-expression: pairs, lists, strings, symbols and integers.
28 We implement lists, strings, symbols and small integers.  Actually,
29 since in C it is easier to work with arrays than with genuine lists,
30 we implement adjustable vectors and treat them as lists (so our
31 terminology is closer to Perl than to Lisp).  We cheat reading
32 pairs: we read them as a three element list, the middle element
33 being the symbol ``.'' (dot).
34 
35 
36 \section{Database format}
37 
38 Below we try to explain essential properties of FriCAS databases,
39 for longer description (including many low level details) look
40 at [[daase.lisp]].
41 General format of FriCAS databases is as follows
42 \begin{verbatim}
43 stamp
44 indirect hunks
45 main data list
46 \end{verbatim}
47 [[stamp]] above is a Lisp pair, its first element is an integer
48 giving byte offset to [[main data list]].  Elements of main
49 data list are themselves lists, which for given database have
50 fixed number (and order) of fields.  For [[interp.daase]] the
51 fields are:
52 \begin{verbatim}
53 constructor name (symbol)
54 operation list
55 constructor modemap
56 modemaps (of added operations)
57 object file (name of the file containing compiled code
58                 of the constructor)
59 constructorcategory
60 niladic (boolean field telling if the constructor takes arguments)
61 abbreviation
62 cosig
63 constructorkind
64 defaultdomain
65 ancestors
66 \end{verbatim}
67 For [[browse.daase]] the fields are:
68 \begin{verbatim}
69 constructor name
70 sourcefile
71 constructorform
72 documentation
73 attributes
74 predicates
75 \end{verbatim}
76 Only simple fields are stored directly, most values are stored in indirect
77 way: given value is replace by an integer giving position (byte offset
78 with respect to the start of the file) of actual value.  Additionally,
79 fields may be compressed.  Compression uses extra file ([[compress.daase]])
80 which contains a single list of values.  Values from other files may
81 be put in the [[compress.daase]] and replaced by minus their index
82 on the [[compress.daase]] list.
83 
84 For example, expanded form of S-expression describing FriCAS [[Type]]
85 (one of the simplest categories) is:
86 \begin{verbatim}
87  (|Type| NIL (((|Type|) (|Category|)) (T |Type|)) NIL "TYPE"
88     (|Join| (CATEGORY |package| (ATTRIBUTE |nil|)))
89     T TYPE (NIL) |category| NIL NIL)
90 \end{verbatim}
91 After replacing composite values by file offsets we get something
92 like:
93 \begin{verbatim}
94  (|Type| 3013278 3013283 3013314 "TYPE" 3013319 T TYPE (NIL)
95     |category| NIL NIL)
96 \end{verbatim}
97 Compression turns this into the following (assuming that position
98 9 in [[compress.daase]] contains symbol [[|category|]] and position
99 1106 in [[compress.daase]] contains symbol [[|Type|]]):
100 \begin{verbatim}
101  (-1106 3013278 3013283 3013314 "TYPE" 3013319 T TYPE (NIL) -9 NIL NIL)
102 \end{verbatim}
103 Remark: When compression is active indirect values are also compressed,
104 so the list at offset 3013283:
105 \begin{verbatim}
106 (((|Type|) (|Category|)) (T |Type|))
107 \end{verbatim}
108 is stored as (assuming that position 1182 in [[compress.daase]] contains
109 symbol [[|Category|]]):
110 \begin{verbatim}
111 (((-1106) (-1182)) (T -1106))
112 \end{verbatim}
113 #endif
114 
115 #include <stdio.h>
116 #include <stdlib.h>
117 #include <limits.h>
118 #include <stdarg.h>
119 #include <string.h>
120 #include <ctype.h>
121 
122 void
fatal(const char * message,...)123 fatal(const char * message, ...)
124 {
125     va_list ap;
126     va_start(ap, message);
127     vfprintf(stderr, message, ap);
128     va_end(ap);
129     exit(1);
130 }
131 
132 void *
my_malloc(size_t n)133 my_malloc(size_t n)
134 {
135     void * p = malloc(n);
136     if (!n) {
137         fatal("Out of memory\n");
138     }
139     return p;
140 }
141 
142 /* Dynamic data structures, Perl/Lisp like. */
143 
144 typedef struct item {
145     int tag;
146 } item;
147 
148 #define TAG_int 1
149 #define TAG_string 2
150 #define TAG_symbol 3
151 #define TAG_list 4
152 
153 int
get_tag(const item * it)154 get_tag(const item * it)
155 {
156     return *((int *)it);
157 }
158 
159 void
assert_tag(const void * it,int tag)160 assert_tag(const void * it, int tag)
161 {
162     if (get_tag(it) != tag) {
163         fatal("Unexpected tag %d (expected %d)\n", get_tag(it), tag);
164     }
165 }
166 
167 typedef struct int_item {
168     int tag;
169     long val;
170 } int_item;
171 
172 /*
173    Counted strings with fill pointer.  Not necessarily null terminated.
174 */
175 
176 typedef struct counted_string {
177    int tag;
178    char * buff;
179    int size;
180    int pos;
181 } counted_string;
182 
183 void
add_char(counted_string * s,char c)184 add_char(counted_string * s, char c)
185 {
186     if (s->pos >= s->size) {
187         if (s->size < INT_MAX/2) {
188             s->size *= 2;
189             s->buff = realloc(s->buff, s->size);
190             if (!s->buff) {
191                 fatal("Out of memory\n");
192             }
193         } else {
194             fatal("Item too long\n");
195         }
196     }
197     s->buff[s->pos] = c;
198     s->pos++;
199 }
200 
201 #define COUNTED_STRING_INITIAL_SIZE 80
202 counted_string *
empty_string(void)203 empty_string(void)
204 {
205     counted_string * s = my_malloc(sizeof(*s));
206     s->buff = my_malloc(COUNTED_STRING_INITIAL_SIZE);
207     s->tag = TAG_string;
208     s->buff[0] = 0;
209     s->size = COUNTED_STRING_INITIAL_SIZE;
210     s->pos = 0;
211     return s;
212 }
213 
214 void
free_counted_string(counted_string * s)215 free_counted_string(counted_string * s)
216 {
217     free(s->buff);
218     free(s);
219 }
220 
221 
222 /*
223    Item lists.
224 */
225 
226 typedef struct item_list {
227     int tag;
228     item * * buff;
229     int size;
230     int pos;
231 } item_list;
232 
233 void
add_item(item_list * sl,item * s)234 add_item(item_list * sl, item * s)
235 {
236     if (sl->pos >= sl->size) {
237         if (sl->size < INT_MAX/(2*sizeof(item *))) {
238             sl->size *= 2;
239             sl->buff = realloc(sl->buff,
240                          sizeof(item *)*sl->size);
241             if (!sl->buff) {
242                 fatal("Out of memory\n");
243             }
244         } else {
245             fatal("List too long\n");
246         }
247     }
248     sl->buff[sl->pos] = s;
249     sl->pos++;
250 }
251 
252 /* 12 allows reading interp.daase entries without reallocation */
253 #define ITEM_LIST_INITIAL_SIZE 12
254 
255 item_list *
empty_list(void)256 empty_list(void)
257 {
258     item_list * sl = my_malloc(sizeof(*sl));
259     sl->buff = my_malloc(ITEM_LIST_INITIAL_SIZE*sizeof(item *));
260     sl->buff[0] = 0;
261     sl->tag = TAG_list;
262     sl->size = ITEM_LIST_INITIAL_SIZE;
263     sl->pos = 0;
264     return sl;
265 }
266 
267 void
268 free_item_list(item_list * sl);
269 
270 void
free_item(item * it)271 free_item(item * it)
272 {
273     switch (get_tag(it)) {
274        case TAG_int:
275            free(it);
276            break;
277        case TAG_string:
278        case TAG_symbol:
279            free_counted_string((counted_string *) it);
280            break;
281        case TAG_list:
282            free_item_list((item_list *) it);
283            break;
284        default:
285            fatal("Unknown tag %d in free_item\n", get_tag(it));
286            break;
287     }
288 }
289 
290 void
free_item_list(item_list * sl)291 free_item_list(item_list * sl)
292 {
293     int i;
294     for (i = 0; i < sl->pos; i++) {
295         free_item(sl->buff[i]);
296     }
297     free(sl->buff);
298     free(sl);
299 }
300 
301 
302 char *
string_val(item * it)303 string_val(item * it)
304 {
305     int tag = get_tag(it);
306     if (tag != TAG_string && tag != TAG_symbol) {
307         fatal("Accessing string value of something not a string or symbol");
308     }
309     return ((counted_string *)it)->buff;
310 }
311 
312 /*
313    Below we implement a simple pretty-printer.  We want nicely
314    indented lists, but to conserve space we try to print
315    multiple items on a single line.  More precisely, when printing
316    list we try first to print the whole list in a single line (starting
317    at given indentation level).  If list does not fit (is too large) we
318    then switch to multiline mode, where we try to fit as many items as
319    possible on a single line.  To support this we fist print to a line
320    sized buffer, and if the result fits we print the buffered content.
321    Items which can not be printed on single line are printed "directly".
322    In direct mode printing routine has to print spaces to indent output,
323    while when printing to the buffer printing routine prints just content,
324    checking that it fits into the buffer.  To handle indentation in
325    we just subtract indentation offset form the buffer size, passing
326    lower limit to printing routines.  Later upper routine prints
327    indentating spaces followed by buffer content.
328 
329    Unfortunately, this approach means that there is significant duplication
330    in printing code, since various modes and cases are quite similar,
331    (but different enough to inhibit sharing).
332 */
333 
334 #define LINE_LENGTH 76
335 int
print_item1(item * it,char * buff,int lim)336 print_item1(item * it, char * buff, int lim)
337 {
338     int tag = get_tag(it);
339     int len;
340     if (tag == TAG_int) {
341         long val = ((int_item *)it)->val;
342         if (buff) {
343             len = snprintf(buff, lim, "%ld", val);
344             if (len > 0 && len < lim) {
345                 return len;
346             } else {
347                 return -1;
348             }
349         }
350         printf("%ld", ((int_item *)it)->val);
351         return -1;
352     } else {
353         char * str = string_val(it);
354         char * s1 = "";
355         int l1 = 0;
356         if (tag == TAG_string) {
357             l1 = 1;
358             s1 = "\"";
359         }
360         if (buff) {
361             int l2 = strlen(str);
362             if (l2 + 2*l1 < lim) {
363                 sprintf(buff, "%s%s%s", s1, str, s1);
364                 return l2 + 2*l1;
365             } else {
366                 return -1;
367             }
368         } else {
369             printf("%s%s%s", s1, str, s1);
370             return -1;
371         }
372     }
373 }
374 
375 void
indent(int offset)376 indent(int offset)
377 {
378     int i;
379     for(i = 0; i < offset; i++) {
380         putchar(' ');
381     }
382 }
383 
384 int
print_items1(item * it,int offset,char * buff,int lim)385 print_items1(item * it, int offset, char * buff, int lim)
386 {
387     int tag = get_tag(it);
388     int i;
389     if (buff) {
390          if (tag == TAG_list) {
391              item_list * il = (item_list *)it;
392              int pos = 1;
393              int i = 0;
394              if (pos >= lim - 1) {
395                  return -1;
396              }
397              buff[0] = '(';
398              for (i = 0 ; i < il->pos; i++) {
399                  int l1;
400                  if (pos >= lim - 1) {
401                      return -1;
402                  }
403                  l1 = print_items1(il->buff[i], offset+3,
404                                          buff+pos, lim - pos - 1);
405                  if (l1 < 0) {
406                      return -1;
407                  }
408                  pos += l1;
409                  buff[pos] = ' ';
410                  pos++;
411              }
412              if (pos >= lim) {
413                  return -1;
414              }
415              buff[pos - 1] = ')';
416              buff[pos] = 0;
417              return pos;
418         } else {
419              return print_item1(it, buff, lim);
420         }
421     }
422     indent(offset);
423     if (tag == TAG_list) {
424        char buff1[LINE_LENGTH];
425        int pos = 0;
426        int lim1 = LINE_LENGTH - offset - 2;
427        item_list * il = (item_list *)it;
428        buff1[0] = 0;
429        for(i = 0; i < il->pos; i++) {
430            int l1 = print_items1(il->buff[i], offset+3,
431                                    buff1 + pos, lim1 - pos - 1);
432            if (l1 < 0) {
433                goto multiline;
434            }
435            pos += l1;
436            buff1[pos] = ' ';
437            pos++;
438        }
439        if (pos>0) {
440            pos--;
441            buff1[pos] = 0;
442        }
443        printf("(%s)", buff1);
444        return -1;
445    multiline:
446        printf("(\n");
447        pos = 0;
448        lim1 = LINE_LENGTH - offset - 3;
449        for(i = 0; i < il->pos; i++) {
450            int l1 = print_items1(il->buff[i], offset + 3,
451                                    buff1+pos, lim1 - pos - 1);
452            if (l1 < 0) {
453                if (pos > 0) {
454                    indent(offset+3);
455                    pos--;
456                    buff1[pos] = 0;
457                    printf("%s\n", buff1);
458                    pos = 0;
459                    l1 = print_items1(il->buff[i], offset + 3, buff1, lim1-1);
460                    if (l1 < 0) {
461                         print_items1(il->buff[i], offset + 3, 0, lim1);
462                         putchar('\n');
463                    } else {
464                         pos = l1;
465                         buff1[pos] = ' ';
466                         pos++;
467                    }
468                } else {
469                    print_items1(il->buff[i], offset+3, 0, lim1);
470                    putchar('\n');
471                }
472            } else {
473                pos += l1;
474                buff1[pos] = ' ';
475                pos++;
476            }
477        }
478        if (pos > 0) {
479            pos--;
480            buff1[pos] = 0;
481            indent(offset+3);
482            printf("%s\n", buff1);
483        }
484        indent(offset);
485        putchar(')');
486        return -1;
487     } else {
488         return print_item1(it, 0, lim);
489     }
490 }
491 
492 void
print_item(item * it)493 print_item(item * it)
494 {
495     print_items1(it, 0, 0, LINE_LENGTH);
496 }
497 
498 void
print_cars(item * it)499 print_cars(item * it)
500 {
501     const item_list * il = (item_list *)it;
502     int i;
503     int tag = get_tag(it);
504     if (tag == TAG_symbol) {
505         counted_string * sy = (counted_string *) it;
506         if (!strcmp(sy->buff, "NIL")) {
507             printf("NIL");
508             return;
509         }
510     }
511     assert_tag(il, TAG_list);
512     for (i = 0; i< il->pos; i++) {
513         item_list * el = (item_list *)(il->buff[i]);
514         assert_tag(el, TAG_list);
515         if (el->pos < 1) {
516            fatal("print_cars trying to print car of empty list\n");
517         }
518         print_items1(el->buff[0], 3, 0, LINE_LENGTH);
519         putchar('\n');
520     }
521 }
522 
523 /*
524    Utilities to read S-expression from files.  Support only things
525    which appear in FriCAS databases.
526 */
527 
528 /* Precondition: File is positioned before item.
529    Postcondition: File is positioned just after the item.
530 */
531 
532 counted_string *
read_string(FILE * file)533 read_string(FILE * file)
534 {
535     int c;
536     counted_string * s = empty_string();
537     while(isspace(c = fgetc(file)))
538         ;
539     if (c != '"') {
540         fatal("String must begin with '\"'\n");
541     }
542     while (1) {
543         c = fgetc(file);
544         if (c == EOF) {
545             fatal("Unexpected EOF\n");
546         }
547         if (c == '"') {
548             add_char(s, 0);
549             return s;
550         } else if (c == '\\') {
551             c = fgetc(file);
552         }
553         add_char(s, c);
554     }
555 }
556 
557 counted_string *
read_symbol(FILE * file)558 read_symbol(FILE * file)
559 {
560     int c;
561     enum {normal, in_ticks} state = normal;
562     counted_string * s = empty_string();
563     s->tag = TAG_symbol;
564     while(isspace(c = fgetc(file)))
565         ;
566     ungetc(c, file);
567     while (1) {
568         c = fgetc(file);
569         if (c == EOF) {
570             fatal("Unexpected EOF\n");
571         }
572         if (state == normal) {
573             if (c == ')' || c == '(' || isspace(c)) {
574                 ungetc(c, file);
575                 add_char(s, 0);
576                 return s;
577             } else if (c == '|') {
578                 state = in_ticks;
579             } else {
580                 add_char(s, c);
581             }
582         } else if (state == in_ticks) {
583             if (c == '|') {
584                 state = normal;
585             } else if (c == '\\') {
586                 c = fgetc(file);
587                 add_char(s, c);
588             } else {
589                 add_char(s, c);
590             }
591         }
592     }
593 }
594 
595 item *
read_item(FILE * file)596 read_item(FILE * file)
597 {
598     int c;
599     while(isspace(c = fgetc(file)))
600         ;
601     if (c == ')') {
602         fatal("Unexpected ')'\n");
603     }
604     if (c == '(') {
605         item_list * res = empty_list();
606         while (1) {
607             while(isspace(c = fgetc(file)))
608                 ;
609             if (c == ')') {
610                 return (item *) res;
611             }
612             ungetc(c, file);
613             add_item(res, read_item(file));
614         }
615     }
616     if (c == '\'') {
617         item_list * res = empty_list();
618         counted_string * s = empty_string();
619         s->tag = TAG_symbol;
620         char * ss = "QUOTE";
621         while (*ss) {
622             add_char(s, *ss);
623             ss++;
624         }
625         add_item(res, (item *)s);
626         add_item(res, read_item(file));
627         return (item *) res;
628     }
629     if (c == '"') {
630         counted_string * res = (ungetc(c, file), read_string(file));
631         return (item *) res;
632     }
633     if (c == '-' || isdigit(c)) {
634         int_item * res = my_malloc(sizeof(*res));
635         char nbuff[25];
636         int i = 0;
637         nbuff[0] = c;
638         i++;
639         c = getc(file);
640         while(isdigit(c)) {
641             if (i + 1 >= sizeof(nbuff)) {
642                 fatal("Integer value too large\n");
643             }
644             nbuff[i] = c;
645             i++;
646             c = getc(file);
647        }
648        nbuff[i] = 0;
649        ungetc(c, file);
650        res->val = atol(nbuff);
651        res->tag = TAG_int;
652        return (item *) res;
653    }
654    {
655        counted_string * res = (ungetc(c, file), read_symbol(file));
656        return (item *) res;
657    }
658 }
659 
660 /* Main program */
661 
662 char * FRICAS;
663 FILE *
open_file(const char * name)664 open_file(const char * name)
665 {
666     FILE * file;
667     long offset;
668     item_list * stamp;
669     char * file_path;
670     if (FRICAS != NULL) {
671         file_path = my_malloc(strlen(FRICAS)+strlen("%s/algebra/%s.daase")
672                               +strlen(name));
673         sprintf(file_path, "%s/algebra/%s.daase", FRICAS, name);
674     } else {
675         file_path = my_malloc(strlen("%s.daase")+strlen(name));
676         sprintf(file_path, "%s.daase", name);
677     }
678     file = fopen(file_path, "rb");
679     if (!file) {
680         fatal("unable to find the file %s\n", file_path);
681     }
682     /* Read main offset */
683     stamp = (item_list *)read_item(file);
684     assert_tag(stamp, TAG_list);
685     if (stamp->pos != 3) {
686        fatal("Bad stamp, have %d entries (should have 3)\n", stamp->pos);
687     }
688     assert_tag(stamp->buff[0], TAG_int);
689     offset = ((int_item *)stamp->buff[0])->val;
690     if (offset <= 0) {
691         fatal("Bad main offset %ld\n", offset);
692     }
693     free_item((item *)stamp);
694     fseek(file, offset, SEEK_SET);
695     return file;
696 }
697 
698 item *
decode(item * s,item_list * ct)699 decode(item * s, item_list * ct)
700 {
701     int tag = get_tag(s);
702     if (tag == TAG_int) {
703         long n = ((int_item *)s)->val;
704         if (n > 0 || 1 - n >= ct->pos) {
705             return s;
706         } else {
707             return ct->buff[1 - n];
708         }
709     }
710     if (tag == TAG_list) {
711         item_list * res = empty_list();
712         item_list * il = (item_list *)s;
713         int i;
714         for (i = 0; i < il->pos; i++) {
715             add_item(res, decode(il->buff[i], ct));
716         }
717         return (item *) res;
718     }
719     return s;
720 }
721 
722 item_list *
scan_file(FILE * file,char * fname,char * key,item_list * ct,int alt_pos)723 scan_file(FILE * file, char * fname, char * key,
724             item_list * ct, int alt_pos)
725 {
726     int c;
727     while(isspace(c = fgetc(file)))
728          ;
729     if (c != '(') {
730         fatal("Main %s.daase list begins with %c\n", fname, c);
731     }
732     while (1) {
733         while(isspace(c = fgetc(file)))
734             ;
735         if (c == '(') {
736             item_list * iml;
737             item * it;
738             ungetc(c, file);
739             it = read_item(file);
740             assert_tag(it, TAG_list);
741             iml = (item_list *)it;
742 #if 0
743             /* Debugging printout */
744             {
745                 int i;
746                 for (i = 0; i < iml->pos; i++) {
747                       /* printf("%s\n", decode (iml->buff[i], ct)); */
748                       printf("%s\n", iml->buff[i]->buff);
749                 }
750                 putchar('\n');
751              }
752 #endif
753              if (!strcmp(key,
754                     string_val(decode(iml->buff[0], ct)))) {
755                  return iml;
756              } else if (alt_pos && !strcmp(key,
757                            string_val(decode(iml->buff[alt_pos], ct)))) {
758                  return iml;
759              }
760              free_item(it);
761         } else if (c != ')') {
762              fatal("Unexpected char %c in interp.daase\n", c);
763         } else {
764              return 0;
765         }
766     }
767 }
768 
769 struct info_slot {char * name; char *abbr; int slot; void (*handler)(item *);};
770 #define BROWSE_SHIFT 100
771 struct info_slot ot[] = {
772 {"constructor", "con", 0, 0},
773 {"sourcefile", "so", 101, 0},
774 {"constructorkind", "ckind", 9, 0},
775 {"niladic", "nilad", 6, 0},
776 {"abbreviation", "abbr", 7, 0},
777 {"defaultdomain", "ddom", 10, 0},
778 {"ancestors", "anc", 11, 0},
779 {"operations", "op", 1, &print_cars},
780 {"cosig", "cos", 8, 0},
781 {"constructorform", "cform", 102, 0},
782 {"constructormodemap", "cmod", 2, 0},
783 {"modemaps", "mod", 3, 0},
784 {"constructorcategory", "ccat", 5, 0},
785 {"documentation", "doc", 103, 0},
786 {"predicates", "pre", 104, 0},
787 };
788 
789 void
printinfo(item_list * iml,item_list * bml,const char * propname,item_list * ct,FILE * interp_file,FILE * browse_file)790 printinfo(item_list * iml, item_list * bml, const char * propname,
791           item_list * ct, FILE * interp_file, FILE * browse_file)
792 {
793     int i;
794     int all = !strcmp(propname, "all");
795     const int nopts = sizeof(ot)/sizeof(struct info_slot);
796     for (i=0; i < nopts; i++) {
797         int slot = ot[i].slot;
798         item * slot_data;
799         FILE * file;
800         if (!all && strcmp(propname, ot[i].name)) {
801             continue;
802         }
803         printf("%s:\n", ot[i].name);
804         if (slot >= BROWSE_SHIFT) {
805             slot_data = bml->buff[slot - BROWSE_SHIFT];
806             file = browse_file;
807         } else {
808             slot_data = iml->buff[slot];
809             file = interp_file;
810         }
811         slot_data = decode(slot_data, ct);
812         if (get_tag(slot_data) == TAG_int) {
813             fseek(file, ((int_item *)slot_data)->val, SEEK_SET);
814             slot_data = decode(read_item (file), ct);
815         }
816         if (ot[i].handler) {
817             (*(ot[i].handler))(slot_data);
818         } else {
819             print_item(slot_data);
820         }
821         putchar('\n');
822     }
823 }
824 
825 void
usage(char * prog)826 usage(char * prog)
827 {
828     int i;
829     const int nopts = sizeof(ot)/sizeof(struct info_slot);
830     printf("Usage:\n"
831            "  %s key\n", prog);
832     printf("or\n"
833            "  %s -property key\n", prog);
834     printf("where key is the name (or abbreviation) and property\n"
835            "is one of the following:\n"
836            "  (al) all\n");
837     for (i=0; i < nopts; i++) {
838         printf("  (%s) %s\n", ot[i].abbr, ot[i].name);
839     }
840     printf("You can give full property name or use abbreviated form\n"
841            "given in parenthesis above.\n");
842     printf("The first form prints all properties.\n");
843     exit(1);
844 }
845 
846 char *
unabbreviate_property(char * propname)847 unabbreviate_property(char * propname)
848 {
849     int i;
850     int j = strlen(propname);
851     const int nopts = sizeof(ot)/sizeof(struct info_slot);
852     if (j < 2) {
853         j = 2;
854     }
855     if (!strncmp(propname, "all", j)) {
856         return "all";
857     }
858     for (i=0; i < nopts; i++) {
859         char * s = ot[i].name;
860         if (!strcmp(propname, ot[i].abbr)
861             || !strcmp(propname, s)) {
862             return s;
863         }
864     }
865     return 0;
866 }
867 int
main(int argc,char ** argv)868 main(int argc, char * * argv)
869 {
870     FILE * compress_file;
871     FILE * interp_file;
872     FILE * browse_file;
873     item_list * ct;
874     item_list * iml;
875     item_list * bml;
876     char * cname;
877     char * propname = "all";
878     long ct_n;
879     FRICAS=(char *)getenv("FRICAS");
880 
881     if (FRICAS == 0)
882         fprintf(stderr, "FRICAS shell variable has no value. "
883                            "using current directory\n");
884     if (argc < 2 || argc > 3) {
885         usage(argv[0]);
886     }
887     if (argc == 3 && argv[1][0] == '-') {
888         cname = argv[2];
889         propname = unabbreviate_property(argv[1]+1);
890         if (!propname) {
891            usage(argv[0]);
892         }
893     } else {
894         cname = argv[1];
895     }
896     compress_file = open_file("compress");
897     ct = (item_list *)read_item(compress_file);
898     assert_tag(ct, TAG_list);
899     if (ct->pos < 1) {
900         fatal("Bad compress_file, no count\n");
901     }
902     {
903         item * ct_n_item = ct->buff[0];
904         assert_tag(ct_n_item, TAG_int);
905         ct_n = ((int_item *)ct->buff[0])->val;
906         if (ct->pos - 1 != ct_n) {
907             fatal("Bad compress_file, count %d, (stored %ld)\n",
908                     ct->pos - 1, ct_n);
909         }
910     }
911     interp_file = open_file("interp");
912     iml = scan_file(interp_file, "interp", cname, ct, 7);
913     if (!iml) {
914         printf("Not found!\n");
915         exit(1);
916     }
917     browse_file = open_file("browse");
918     bml = scan_file(browse_file, "browse",
919                       string_val(decode(iml->buff[0], ct)), ct, 0);
920     if (!bml) {
921         printf("Not found!\n");
922         exit(1);
923     }
924     printinfo(iml, bml, propname, ct, interp_file, browse_file);
925     return 0;
926 }
927