]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/deroff.c
togif: -E flag to read animation from stdin
[plan9front.git] / sys / src / cmd / deroff.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4
5 /*
6  * Deroff command -- strip troff, eqn, and tbl sequences from
7  * a file.  Has three flags argument, -w, to cause output one word per line
8  * rather than in the original format.
9  * -mm (or -ms) causes the corresponding macro's to be interpreted
10  * so that just sentences are output
11  * -ml  also gets rid of lists.
12  * -i causes deroff to ignore .so and .nx commands.
13  * Deroff follows .so and .nx commands, removes contents of macro
14  * definitions, equations (both .EQ ... .EN and $...$),
15  * Tbl command sequences, and Troff backslash vconstructions.
16  * 
17  * All input is through the C macro; the most recently read character is in c.
18  */
19
20 /*
21 #define C       ((c = Bgetrune(infile)) < 0?\
22                         eof():\
23                         ((c == ldelim) && (filesp == files)?\
24                                 skeqn():\
25                                 (c == '\n'?\
26                                         (linect++,c):\
27                                                 c)))
28
29 #define C1      ((c = Bgetrune(infile)) == Beof?\
30                         eof():\
31                         (c == '\n'?\
32                                 (linect++,c):\
33                                 c))
34 */
35
36 /* lose those macros! */
37 #define C       fC()
38 #define C1      fC1()
39
40 #define SKIP    while(C != '\n') 
41 #define SKIP1   while(C1 != '\n')
42 #define SKIP_TO_COM             SKIP;\
43                                 SKIP;\
44                                 pc=c;\
45                                 while(C != '.' || pc != '\n' || C > 'Z')\
46                                                 pc=c
47
48 #define YES             1
49 #define NO              0
50 #define MS              0
51 #define MM              1
52 #define ONE             1
53 #define TWO             2
54
55 #define NOCHAR          -2
56 #define EXTENDED        -1              /* All runes above 0x7F */
57 #define SPECIAL         0
58 #define APOS            1
59 #define PUNCT           2
60 #define DIGIT           3
61 #define LETTER          4
62
63
64 int     linect  = 0;
65 int     wordflag= NO;
66 int     underscoreflag = NO;
67 int     msflag  = NO;
68 int     iflag   = NO;
69 int     mac     = MM;
70 int     disp    = 0;
71 int     inmacro = NO;
72 int     intable = NO;
73 int     eqnflag = 0;
74
75 #define MAX_ASCII       0X80
76
77 char    chars[MAX_ASCII];       /* SPECIAL, PUNCT, APOS, DIGIT, or LETTER */
78
79 Rune    line[30000];
80 Rune*   lp;
81
82 long    c;
83 long    pc;
84 int     ldelim  = NOCHAR;
85 int     rdelim  = NOCHAR;
86
87
88 char**  argv;
89
90 char    fname[50];
91 Biobuf* files[15];
92 Biobuf**filesp;
93 Biobuf* infile;
94 char*   devnull = "/dev/null";
95 Biobuf  *infile;
96 Biobuf  bout;
97
98 long    skeqn(void);
99 Biobuf* opn(char *p);
100 int     eof(void);
101 int     charclass(int);
102 void    getfname(void);
103 void    fatal(char *s, char *p);
104 void    usage(void);
105 void    work(void);
106 void    putmac(Rune *rp, int vconst);
107 void    regline(int macline, int vconst);
108 void    putwords(void);
109 void    comline(void);
110 void    macro(void);
111 void    eqn(void);
112 void    tbl(void);
113 void    stbl(void);
114 void    sdis(char a1, char a2);
115 void    sce(void);
116 void    backsl(void);
117 char*   copys(char *s);
118 void    refer(int c1);
119 void    inpic(void);
120
121 int
122 fC(void)
123 {
124         c = Bgetrune(infile);
125         if(c < 0)
126                 return eof();
127         if(c == ldelim && filesp == files)
128                 return skeqn();
129         if(c == '\n')
130                 linect++;
131         return c;
132 }
133
134 int
135 fC1(void)
136 {
137         c = Bgetrune(infile);
138         if(c == Beof)
139                 return eof();
140         if(c == '\n')
141                 linect++;
142         return c;
143 }
144
145 void
146 main(int argc, char *av[])
147 {
148         int i;
149         char *f;
150
151         argv = av;
152         Binit(&bout, 1, OWRITE);
153         ARGBEGIN{
154         case 'w':
155                 wordflag = YES;
156                 break;
157         case '_':
158                 wordflag = YES;
159                 underscoreflag = YES;
160                 break;
161         case 'm':
162                 msflag = YES;
163                 if(f = ARGF())
164                         switch(*f)
165                         {
166                         case 'm':       mac = MM; break;
167                         case 's':       mac = MS; break;
168                         case 'l':       disp = 1; break;
169                         default:        usage();
170                         }
171                 else
172                         usage();
173                 break;
174         case 'i':
175                 iflag = YES;
176                 break;
177         default:
178                 usage();
179         }ARGEND
180         if(*argv)
181                 infile = opn(*argv++);
182         else{
183                 infile = malloc(sizeof(Biobuf));
184                 Binit(infile, 0, OREAD);
185         }
186         files[0] = infile;
187         filesp = &files[0];
188
189         for(i='a'; i<='z' ; ++i)
190                 chars[i] = LETTER;
191         for(i='A'; i<='Z'; ++i)
192                 chars[i] = LETTER;
193         for(i='0'; i<='9'; ++i)
194                 chars[i] = DIGIT;
195         chars['\''] = APOS;
196         chars['&'] = APOS;
197         chars['\b'] = APOS;
198         chars['.'] = PUNCT;
199         chars[','] = PUNCT;
200         chars[';'] = PUNCT;
201         chars['?'] = PUNCT;
202         chars[':'] = PUNCT;
203         work();
204 }
205
206 long
207 skeqn(void)
208 {
209         while(C1 != rdelim)
210                 if(c == '\\')
211                         c = C1;
212                 else if(c == '"')
213                         while(C1 != '"')
214                                 if(c == '\\') 
215                                         C1;
216         if (msflag)
217                 eqnflag = 1;
218         return(c = ' ');
219 }
220
221 Biobuf*
222 opn(char *p)
223 {
224         Biobuf *fd;
225
226         while ((fd = Bopen(p, OREAD)) == 0) {
227                 if(msflag || p == devnull)
228                         fatal("Cannot open file %s - quitting\n", p);
229                 else {
230                         fprint(2, "Deroff: Cannot open file %s - continuing\n", p);
231                         p = devnull;
232                 }
233         }
234         linect = 0;
235         return(fd);
236 }
237
238 int
239 eof(void)
240 {
241         if(Bfildes(infile) != 0)
242                 Bterm(infile);
243         if(filesp > files)
244                 infile = *--filesp;
245         else
246         if(*argv)
247                 infile = opn(*argv++);
248         else
249                 exits(0);
250         return(C);
251 }
252
253 void
254 getfname(void)
255 {
256         char *p;
257         Rune r;
258         Dir *dir;
259         struct chain
260         { 
261                 struct  chain*  nextp; 
262                 char*   datap; 
263         } *q;
264
265         static struct chain *namechain= 0;
266
267         while(C == ' ')
268                 ;
269         for(p = fname; (r=c) != '\n' && r != ' ' && r != '\t' && r != '\\'; C)
270                 p += runetochar(p, &r);
271         *p = '\0';
272         while(c != '\n')
273                 C;
274         if(!strcmp(fname, "/sys/lib/tmac/tmac.cs")
275                         || !strcmp(fname, "/sys/lib/tmac/tmac.s")) {
276                 fname[0] = '\0';
277                 return;
278         }
279         dir = dirstat(fname);
280         if(dir!=nil && ((dir->mode & DMDIR) || dir->type != 'M')) {
281                 free(dir);
282                 fname[0] = '\0';
283                 return;
284         }
285         free(dir);
286         /*
287          * see if this name has already been used
288          */
289
290         for(q = namechain; q; q = q->nextp)
291                 if( !strcmp(fname, q->datap)) {
292                         fname[0] = '\0';
293                         return;
294                 }
295         q = (struct chain*)malloc(sizeof(struct chain));
296         q->nextp = namechain;
297         q->datap = copys(fname);
298         namechain = q;
299 }
300
301 void
302 usage(void)
303 {
304         fprint(2,"usage: deroff [-nw_pi] [-m (m s l)] [file ...] \n");
305         exits("usage");
306 }
307
308 void
309 fatal(char *s, char *p)
310 {
311         fprint(2, "deroff: ");
312         fprint(2, s, p);
313         exits(s);
314 }
315
316 void
317 work(void)
318 {
319
320         for(;;) {
321                 eqnflag = 0;
322                 if(C == '.'  ||  c == '\'')
323                         comline();
324                 else
325                         regline(NO, TWO);
326         }
327 }
328
329 void
330 regline(int macline, int vconst)
331 {
332         line[0] = c;
333         lp = line;
334         for(;;) {
335                 if(c == '\\') {
336                         *lp = ' ';
337                         backsl();
338                         if(c == '%')    /* no blank for hyphenation char */
339                                 lp--;
340                 }
341                 if(c == '\n')
342                         break;
343                 if(intable && c=='T') {
344                         *++lp = C;
345                         if(c=='{' || c=='}') {
346                                 lp[-1] = ' ';
347                                 *lp = C;
348                         }
349                 } else {
350                         if(msflag == 1 && eqnflag == 1) {
351                                 eqnflag = 0;
352                                 *++lp = 'x';
353                         }
354                         *++lp = C;
355                 }
356         }
357         *lp = '\0';
358         if(lp != line) {
359                 if(wordflag)
360                         putwords();
361                 else
362                 if(macline)
363                         putmac(line,vconst);
364                 else
365                         Bprint(&bout, "%S\n", line);
366         }
367 }
368
369 void
370 putmac(Rune *rp, int vconst)
371 {
372         Rune *t;
373         int found;
374         Rune last;
375
376         found = 0;
377         last = 0;
378         while(*rp) {
379                 while(*rp == ' ' || *rp == '\t')
380                         Bputrune(&bout, *rp++);
381                 for(t = rp; *t != ' ' && *t != '\t' && *t != '\0'; t++)
382                         ;
383                 if(*rp == '\"')
384                         rp++;
385                 if(t > rp+vconst && charclass(*rp) == LETTER
386                                 && charclass(rp[1]) == LETTER) {
387                         while(rp < t)
388                                 if(*rp == '\"')
389                                         rp++;
390                                 else
391                                         Bputrune(&bout, *rp++);
392                         last = t[-1];
393                         found++;
394                 } else
395                 if(found && charclass(*rp) == PUNCT && rp[1] == '\0')
396                         Bputrune(&bout, *rp++);
397                 else {
398                         last = t[-1];
399                         rp = t;
400                 }
401         }
402         Bputc(&bout, '\n');
403         if(msflag && charclass(last) == PUNCT)
404                 Bprint(&bout, " %C\n", last);
405 }
406
407 /*
408  * break into words for -w option
409  */
410 void
411 putwords(void)
412 {
413         Rune *p, *p1;
414         int i, nlet;
415
416
417         for(p1 = line;;) {
418                 /*
419                  * skip initial specials ampersands and apostrophes
420                  */
421                 while((i = charclass(*p1)) != EXTENDED && i < DIGIT)
422                         if(*p1++ == '\0')
423                                 return;
424                 nlet = 0;
425                 for(p = p1; (i = charclass(*p)) != SPECIAL || (underscoreflag && *p=='_'); p++)
426                         if(i == LETTER || (underscoreflag && *p == '_'))
427                                 nlet++;
428                 /*
429                  * MDM definition of word
430                  */
431                 if(nlet > 1) {
432                         /*
433                          * delete trailing ampersands and apostrophes
434                          */
435                         while(*--p == '\'' || *p == '&'
436                                            || charclass(*p) == PUNCT)
437                                 ;
438                         while(p1 <= p)
439                                 Bputrune(&bout, *p1++);
440                         Bputc(&bout, '\n');
441                 } else
442                         p1 = p;
443         }
444 }
445
446 void
447 comline(void)
448 {
449         long c1, c2;
450
451         while(C==' ' || c=='\t')
452                 ;
453 comx:
454         if((c1=c) == '\n')
455                 return;
456         c2 = C;
457         if(c1=='.' && c2!='.')
458                 inmacro = NO;
459         if(msflag && c1 == '['){
460                 refer(c2);
461                 return;
462         }
463         if(c2 == '\n')
464                 return;
465         if(c1 == '\\' && c2 == '\"')
466                 SKIP;
467         else
468         if (filesp==files && c1=='E' && c2=='Q')
469                         eqn();
470         else
471         if(filesp==files && c1=='T' && (c2=='S' || c2=='C' || c2=='&')) {
472                 if(msflag)
473                         stbl(); 
474                 else
475                         tbl();
476         }
477         else
478         if(c1=='T' && c2=='E')
479                 intable = NO;
480         else if (!inmacro &&
481                         ((c1 == 'd' && c2 == 'e') ||
482                          (c1 == 'i' && c2 == 'g') ||
483                          (c1 == 'a' && c2 == 'm')))
484                                 macro();
485         else
486         if(c1=='s' && c2=='o') {
487                 if(iflag)
488                         SKIP;
489                 else {
490                         getfname();
491                         if(fname[0]) {
492                                 if(infile = opn(fname))
493                                         *++filesp = infile;
494                                 else infile = *filesp;
495                         }
496                 }
497         }
498         else
499         if(c1=='n' && c2=='x')
500                 if(iflag)
501                         SKIP;
502                 else {
503                         getfname();
504                         if(fname[0] == '\0')
505                                 exits(0);
506                         if(Bfildes(infile) != 0)
507                                 Bterm(infile);
508                         infile = *filesp = opn(fname);
509                 }
510         else
511         if(c1 == 't' && c2 == 'm')
512                 SKIP;
513         else
514         if(c1=='h' && c2=='w')
515                 SKIP; 
516         else
517         if(msflag && c1 == 'T' && c2 == 'L') {
518                 SKIP_TO_COM;
519                 goto comx; 
520         }
521         else
522         if(msflag && c1=='N' && c2 == 'R')
523                 SKIP;
524         else
525         if(msflag && c1 == 'A' && (c2 == 'U' || c2 == 'I')){
526                 if(mac==MM)SKIP;
527                 else {
528                         SKIP_TO_COM;
529                         goto comx; 
530                 }
531         } else
532         if(msflag && c1=='F' && c2=='S') {
533                 SKIP_TO_COM;
534                 goto comx; 
535         }
536         else
537         if(msflag && (c1=='S' || c1=='N') && c2=='H') {
538                 SKIP_TO_COM;
539                 goto comx; 
540         } else
541         if(c1 == 'U' && c2 == 'X') {
542                 if(wordflag)
543                         Bprint(&bout, "UNIX\n");
544                 else
545                         Bprint(&bout, "UNIX ");
546         } else
547         if(msflag && c1=='O' && c2=='K') {
548                 SKIP_TO_COM;
549                 goto comx; 
550         } else
551         if(msflag && c1=='N' && c2=='D')
552                 SKIP;
553         else
554         if(msflag && mac==MM && c1=='H' && (c2==' '||c2=='U'))
555                 SKIP;
556         else
557         if(msflag && mac==MM && c2=='L') {
558                 if(disp || c1=='R')
559                         sdis('L', 'E');
560                 else {
561                         SKIP;
562                         Bprint(&bout, " .");
563                 }
564         } else
565         if(!msflag && c1=='P' && c2=='S') {
566                 inpic();
567         } else
568         if(msflag && (c1=='D' || c1=='N' || c1=='K'|| c1=='P') && c2=='S') { 
569                 sdis(c1, 'E'); 
570         } else
571         if(msflag && (c1 == 'K' && c2 == 'F')) { 
572                 sdis(c1,'E'); 
573         } else
574         if(msflag && c1=='n' && c2=='f')
575                 sdis('f','i');
576         else
577         if(msflag && c1=='c' && c2=='e')
578                 sce();
579         else {
580                 if(c1=='.' && c2=='.') {
581                         if(msflag) {
582                                 SKIP;
583                                 return;
584                         }
585                         while(C == '.')
586                                 ;
587                 }
588                 inmacro++;
589                 if(c1 <= 'Z' && msflag)
590                         regline(YES,ONE);
591                 else {
592                         if(wordflag)
593                                 C;
594                         regline(YES,TWO);
595                 }
596                 inmacro--;
597         }
598 }
599
600 void
601 macro(void)
602 {
603         if(msflag) {
604                 do { 
605                         SKIP1; 
606                 } while(C1 != '.' || C1 != '.' || C1 == '.');
607                 if(c != '\n')
608                         SKIP;
609                 return;
610         }
611         SKIP;
612         inmacro = YES;
613 }
614
615 void
616 sdis(char a1, char a2)
617 {
618         int c1, c2;
619         int eqnf;
620         int lct;
621
622         if(a1 == 'P'){
623                 while(C1 == ' ')
624                         ;
625                 if(c == '<') {
626                         SKIP1;
627                         return;
628                 }
629         }
630         lct = 0;
631         eqnf = 1;
632         if(c != '\n')
633                 SKIP1;
634         for(;;) {
635                 while(C1 != '.')
636                         if(c == '\n')
637                                 continue;
638                         else
639                                 SKIP1;
640                 if((c1=C1) == '\n')
641                         continue;
642                 if((c2=C1) == '\n') {
643                         if(a1 == 'f' && (c1 == 'P' || c1 == 'H'))
644                                 return;
645                         continue;
646                 }
647                 if(c1==a1 && c2 == a2) {
648                         SKIP1;
649                         if(lct != 0){
650                                 lct--;
651                                 continue;
652                         }
653                         if(eqnf)
654                                 Bprint(&bout, " .");
655                         Bputc(&bout, '\n');
656                         return;
657                 } else
658                 if(a1 == 'L' && c2 == 'L') {
659                         lct++;
660                         SKIP1;
661                 } else
662                 if(a1 == 'D' && c1 == 'E' && c2 == 'Q') {
663                         eqn(); 
664                         eqnf = 0;
665                 } else
666                 if(a1 == 'f') {
667                         if((mac == MS && c2 == 'P') ||
668                                 (mac == MM && c1 == 'H' && c2 == 'U')){
669                                 SKIP1;
670                                 return;
671                         }
672                         SKIP1;
673                 }
674                 else
675                         SKIP1;
676         }
677 }
678
679 void
680 tbl(void)
681 {
682         while(C != '.')
683                 ;
684         SKIP;
685         intable = YES;
686 }
687
688 void
689 stbl(void)
690 {
691         while(C != '.')
692                 ;
693         SKIP_TO_COM;
694         if(c != 'T' || C != 'E') {
695                 SKIP;
696                 pc = c;
697                 while(C != '.' || pc != '\n' || C != 'T' || C != 'E')
698                         pc = c;
699         }
700 }
701
702 void
703 eqn(void)
704 {
705         long c1, c2;
706         int dflg;
707         char last;
708
709         last = 0;
710         dflg = 1;
711         SKIP;
712
713         for(;;) {
714                 if(C1 == '.'  || c == '\'') {
715                         while(C1==' ' || c=='\t')
716                                 ;
717                         if(c=='E' && C1=='N') {
718                                 SKIP;
719                                 if(msflag && dflg) {
720                                         Bputc(&bout, 'x');
721                                         Bputc(&bout, ' ');
722                                         if(last) {
723                                                 Bputc(&bout, last); 
724                                                 Bputc(&bout, '\n'); 
725                                         }
726                                 }
727                                 return;
728                         }
729                 } else
730                 if(c == 'd') {
731                         if(C1=='e' && C1=='l')
732                                 if(C1=='i' && C1=='m') {
733                                         while(C1 == ' ')
734                                                 ;
735                                         if((c1=c)=='\n' || (c2=C1)=='\n' ||
736                                           (c1=='o' && c2=='f' && C1=='f')) {
737                                                 ldelim = NOCHAR;
738                                                 rdelim = NOCHAR;
739                                         } else {
740                                                 ldelim = c1;
741                                                 rdelim = c2;
742                                         }
743                                 }
744                         dflg = 0;
745                 }
746                 if(c != '\n')
747                         while(C1 != '\n') { 
748                                 if(chars[c] == PUNCT)
749                                         last = c;
750                                 else
751                                 if(c != ' ')
752                                         last = 0;
753                         }
754         }
755 }
756
757 /*
758  * skip over a complete backslash vconstruction
759  */
760 void
761 backsl(void)
762 {
763         int bdelim;
764
765 sw:  
766         switch(C1)
767         {
768         case '"':
769                 SKIP1;
770                 return;
771
772         case 's':
773                 if(C1 == '\\')
774                         backsl();
775                 else {
776                         while(C1>='0' && c<='9')
777                                 ;
778                         Bungetrune(infile);
779                         c = '0';
780                 }
781                 lp--;
782                 return;
783
784         case 'f':
785         case 'n':
786         case '*':
787                 if(C1 != '(')
788                         return;
789
790         case '(':
791                 if(msflag) {
792                         if(C == 'e') {
793                                 if(C1 == 'm') {
794                                         *lp = '-';
795                                         return;
796                                 }
797                         } else
798                         if(c != '\n')
799                                 C1;
800                         return;
801                 }
802                 if(C1 != '\n')
803                         C1;
804                 return;
805
806         case '$':
807                 C1;     /* discard argument number */
808                 return;
809
810         case 'b':
811         case 'x':
812         case 'v':
813         case 'h':
814         case 'w':
815         case 'o':
816         case 'l':
817         case 'L':
818                 if((bdelim=C1) == '\n')
819                         return;
820                 while(C1!='\n' && c!=bdelim)
821                         if(c == '\\')
822                                 backsl();
823                 return;
824
825         case '\\':
826                 if(inmacro)
827                         goto sw;
828         default:
829                 return;
830         }
831 }
832
833 char*
834 copys(char *s)
835 {
836         char *t, *t0;
837
838         if((t0 = t = malloc((strlen(s)+1))) == 0)
839                 fatal("Cannot allocate memory", (char*)0);
840         while(*t++ = *s++)
841                 ;
842         return(t0);
843 }
844
845 void
846 sce(void)
847 {
848         int n = 1;
849
850         while (C != L'\n' && !(L'0' <= c && c <= L'9'))
851                 ;
852         if (c != L'\n') {
853                 for (n = c-L'0';'0' <= C && c <= L'9';)
854                         n = n*10 + c-L'0';
855         }
856         while(n) {
857                 if(C == '.') {
858                         if(C == 'c') {
859                                 if(C == 'e') {
860                                         while(C == ' ')
861                                                 ;
862                                         if(c == '0') {
863                                                 SKIP;
864                                                 break;
865                                         } else
866                                                 SKIP;
867                                 } else
868                                         SKIP;
869                         } else
870                         if(c == 'P' || C == 'P') {
871                                 if(c != '\n')
872                                         SKIP;
873                                 break;
874                         } else
875                                 if(c != '\n')
876                                         SKIP;
877                 } else {
878                         SKIP;
879                         n--;
880                 }
881         }
882 }
883
884 void
885 refer(int c1)
886 {
887         int c2;
888
889         if(c1 != '\n')
890                 SKIP;
891         c2 = 0;
892         for(;;) {
893                 if(C != '.')
894                         SKIP;
895                 else {
896                         if(C != ']')
897                                 SKIP;
898                         else {
899                                 while(C != '\n')
900                                         c2 = c;
901                                 if(charclass(c2) == PUNCT)
902                                         Bprint(&bout, " %C",c2);
903                                 return;
904                         }
905                 }
906         }
907 }
908
909 void
910 inpic(void)
911 {
912         int c1;
913         Rune *p1;
914
915 /*      SKIP1;*/
916         while(C1 != '\n')
917                 if(c == '<'){
918                         SKIP1;
919                         return;
920                 }
921         p1 = line;
922         c = '\n';
923         for(;;) {
924                 c1 = c;
925                 if(C1 == '.' && c1 == '\n') {
926                         if(C1 != 'P' || C1 != 'E') {
927                                 if(c != '\n'){
928                                         SKIP1;
929                                         c = '\n';
930                                 }
931                                 continue;
932                         }
933                         SKIP1;
934                         return;
935                 } else
936                 if(c == '\"') {
937                         while(C1 != '\"') {
938                                 if(c == '\\') {
939                                         if(C1 == '\"')
940                                                 continue;
941                                         Bungetrune(infile);
942                                         backsl();
943                                 } else
944                                         *p1++ = c;
945                         }
946                         *p1++ = ' ';
947                 } else
948                 if(c == '\n' && p1 != line) {
949                         *p1 = '\0';
950                         if(wordflag)
951                                 putwords();
952                         else
953                                 Bprint(&bout, "%S\n\n", line);
954                         p1 = line;
955                 }
956         }
957 }
958
959 int
960 charclass(int c)
961 {
962         if(c < MAX_ASCII)
963                 return chars[c];
964         switch(c){
965         case 0x2013: case 0x2014:       /* en dash, em dash */
966                 return SPECIAL;
967         }
968         return EXTENDED;
969 }