]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/html2ms.c
devpccard, pci: fix pccard support and handle pci expansion roms
[plan9front.git] / sys / src / cmd / html2ms.c
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <bio.h>
5
6 typedef struct Tag Tag;
7 typedef struct Attr Attr;
8 typedef struct Text Text;
9
10 struct Attr {
11         char    attr[64];
12         char    val[256-64];
13 };
14
15 struct Tag {
16         Tag     *up;
17         char    tag[32];
18         Attr    attr[16];
19         int     nattr;
20         int     opening;
21         int     closing;
22
23         void    (*close)(Text *, Tag *);
24         union {
25                 void    *aux;
26         };
27 };
28
29 struct Text {
30         char*   fontstyle;
31         char*   fontsize;
32         int     pre;
33         int     pos;
34         int     space;
35         int     output;
36
37         char    *bp;
38         char    *wp;
39         int     nb;
40 };
41
42 void eatwhite(void);
43 void parsetext(Text *, Tag *);
44 int parsetag(Tag *);
45 int parseattr(Attr *);
46 void flushtext(Text *);
47 char* getattr(Tag *, char *);
48 int gotattr(Tag *, char *, char *);
49 int gotstyle(Tag *, char *, char *);
50 void reparent(Text *, Tag *, Tag *);
51 void debugtag(Tag *, char *);
52
53 Biobuf in;
54
55 void
56 emitbuf(Text *text, char *buf, int nbuf)
57 {
58         int nw;
59
60         nw = text->wp - text->bp;
61         if((text->nb - nw) < nbuf){
62                 if(nbuf < 4096)
63                         text->nb = nw + 4096;
64                 else
65                         text->nb = nw + nbuf;
66                 text->bp = realloc(text->bp, text->nb);
67                 text->wp = text->bp + nw;
68         }
69         memmove(text->wp, buf, nbuf);
70         text->wp += nbuf;
71 }
72
73 void
74 emitrune(Text *text, Rune r)
75 {
76         char buf[UTFmax+1];
77
78         if(r == '\r' || r =='\n'){
79                 text->pos = 0;
80                 text->space = 0;
81         }else
82                 text->pos++;
83         emitbuf(text, buf, runetochar(buf, &r));
84 }
85
86 void
87 emit(Text *text, char *fmt, ...)
88 {
89         Rune buf[64];
90         va_list a;
91         int i;
92
93         if(fmt[0] == '.' && text->pos)
94                 emitrune(text, '\n');
95         va_start(a, fmt);
96         runevsnprint(buf, nelem(buf), fmt, a);
97         va_end(a);
98         for(i=0; buf[i]; i++)
99                 emitrune(text, buf[i]);
100 }
101
102 void
103 restoreoutput(Text *text, Tag *)
104 {
105         text->output = 1;
106 }
107
108 void
109 ongarbage(Text *text, Tag *tag)
110 {
111         if(text->output == 0)
112                 return;
113         tag->close = restoreoutput;
114         text->output = 0;
115 }
116
117 void
118 onmeta(Text *, Tag *tag)
119 {
120         tag->closing = 1;
121 }
122
123 void
124 onp(Text *text, Tag *)
125 {
126         emit(text, ".LP\n");
127 }
128
129 void
130 restorepre(Text *text, Tag *)
131 {
132         text->pre = 0;
133         emit(text, ".DE\n");
134 }
135
136 void
137 onpre(Text *text, Tag *tag)
138 {
139         if(text->pre)
140                 return;
141         tag->close = restorepre;
142         text->pre = 1;
143         emit(text, ".DS L\n");
144 }
145
146 void
147 onli(Text *text, Tag *tag)
148 {
149         if(tag->up && cistrcmp(tag->up->tag, "ol") == 0)
150                 emit(text, ".IP\n");
151         else
152                 emit(text, ".IP \\(bu\n");
153         if(tag->up)
154                 tag->up->close = onp;
155 }
156
157 void
158 onh(Text *text, Tag *tag)
159 {
160         emit(text, ".SH\n");
161         tag->close = onp;
162 }
163
164 void
165 onbr(Text *text, Tag *tag)
166 {
167         tag->closing = 1;
168         emit(text, ".br\n");
169         if(cistrcmp(tag->tag, "hr") == 0)
170                 emit(text, "\\l'5i'\n.br\n");
171 }
172
173 void
174 fontstyle(Text *text, char *style)
175 {
176         if(strcmp(text->fontstyle, style) == 0)
177                 return;
178         text->fontstyle = style;
179         emit(text, "\\f%s", style);
180 }
181
182 void
183 fontsize(Text *text, char *size)
184 {
185         if(strcmp(text->fontsize, size) == 0)
186                 return;
187         text->fontsize = size;
188         emit(text, ".%s\n", size);
189 }
190
191 void
192 restorefontstyle(Text *text, Tag *tag)
193 {
194         fontstyle(text, tag->aux);
195 }
196
197 void
198 restorefontsize(Text *text, Tag *tag)
199 {
200         fontsize(text, tag->aux);
201 }
202
203 void
204 oni(Text *text, Tag *tag)
205 {
206         tag->aux = text->fontstyle;
207         tag->close = restorefontstyle;
208         fontstyle(text, "I");
209 }
210
211 void
212 onb(Text *text, Tag *tag)
213 {
214         tag->aux = text->fontstyle;
215         tag->close = restorefontstyle;
216         fontstyle(text, "B");
217 }
218
219 void onsmall(Text *text, Tag *tag);
220 void onsup(Text *text, Tag *tag);
221
222 void
223 onsub(Text *text, Tag *tag)
224 {
225         emit(text, "\\v\'0.5\'");
226         if(cistrcmp(tag->tag, "sub") == 0){
227                 emit(text, "\\x\'0.5\'");
228                 onsmall(text, tag);
229         } else
230                 restorefontsize(text, tag);
231         tag->close = onsup;
232 }
233
234 void
235 onsup(Text *text, Tag *tag)
236 {
237         emit(text, "\\v\'-0.5\'");
238         if(cistrcmp(tag->tag, "sup") == 0){
239                 emit(text, "\\x\'-0.5\'");
240                 onsmall(text, tag);
241         }else
242                 restorefontsize(text, tag);
243         tag->close = onsub;
244 }
245
246 /*
247  * this is poor mans CSS handler.
248  */
249 void
250 onspan(Text *text, Tag *tag)
251 {
252         Attr *a;
253
254         if(!tag->opening)
255                 return;
256
257         for(a=tag->attr; a < tag->attr+tag->nattr; a++){
258                 if(cistrcmp(a->attr, "class") != 0)
259                         continue;
260
261                 if(cistrcmp(a->val, "bold") == 0){
262                         onb(text, tag);
263                         return;
264                 }
265                 if(cistrcmp(a->val, "italic") == 0){
266                         oni(text, tag);
267                         return;
268                 }
269                 if(cistrcmp(a->val, "subscript") == 0){
270                         strcpy(tag->tag, "sub");
271                         onsub(text, tag);
272                         strcpy(tag->tag, "span");
273                         return;
274                 }
275                 if(cistrcmp(a->val, "superscript") == 0){
276                         strcpy(tag->tag, "sup");
277                         onsup(text, tag);
278                         strcpy(tag->tag, "span");
279                         return;
280                 }
281         }
282 }
283
284 void
285 ontt(Text *text, Tag *tag)
286 {
287         tag->aux = text->fontstyle;
288         tag->close = restorefontstyle;
289         fontstyle(text, "C");
290 }
291
292 void
293 onsmall(Text *text, Tag *tag)
294 {
295         tag->aux = text->fontsize;
296         tag->close = restorefontsize;
297         fontsize(text, "SM");
298 }
299
300 void
301 onbig(Text *text, Tag *tag)
302 {
303         tag->aux = text->fontsize;
304         tag->close = restorefontsize;
305         fontsize(text, "LG");
306 }
307
308 void
309 endquote(Text *text, Tag *tag)
310 {
311         if(cistrcmp(tag->tag, "q") == 0)
312                 emitrune(text, '"');
313         emit(text, ".QE\n");
314 }
315
316 void
317 onquote(Text *text, Tag *tag)
318 {
319         tag->close = endquote;
320         if(cistrcmp(tag->tag, "q") == 0)
321                 emit(text, ".QS\n\"");
322         else
323                 emit(text, ".QP\n");
324 }
325
326 typedef struct Table Table;
327 struct Table
328 {
329         char    *bp;
330         int     nb;
331
332         Table   *next;
333         Table   *prev;
334         int     enclose;
335         int     brk;
336
337         char    fmt[4];
338
339         Text    save;
340 };
341
342 Tag*
343 tabletag(Tag *tag)
344 {
345         if(tag == nil)
346                 return nil;
347         if(cistrcmp(tag->tag, "table") == 0)
348                 return tag;
349         return tabletag(tag->up);
350 }
351
352 void
353 dumprows(Text *text, Table *s, Table *e)
354 {
355         
356         for(; s != e; s = s->next){
357                 if(s->enclose)
358                         emit(text, "T{\n");
359                 if(s->nb <= 0)
360                         emit(text, "\\ ");
361                 else
362                         emitbuf(text, s->bp, s->nb);
363                 if(s->enclose)
364                         emit(text, "\nT}");
365                 emitrune(text, s->brk ? '\n' : '\t');
366         }
367 }
368
369 void
370 endtable(Text *text, Tag *tag)
371 {
372         int i, cols, rows;
373         Table *t, *h, *s;
374         Tag *tt;
375
376         /* reverse list */
377         h = nil;
378         t = tag->aux;
379         for(; t; t = t->prev){
380                 t->next = h;
381                 h = t;
382         }
383
384         /*
385          * nested table case, add our cells to the next table up.
386          * this is the best we can do, tbl doesnt support nesting
387          */
388         if(tt = tabletag(tag->up)){
389                 while(t = h){
390                         h = h->next;
391                         t->next = nil;
392                         t->prev = tt->aux;
393                         tt->aux = t;
394                 }
395                 return;
396         }
397
398         cols = 0;
399         rows = 0;
400         for(i = 0, t = h; t; t = t->next){
401                 i++;
402                 if(t->brk){
403                         rows++;
404                         if(i > cols)
405                                 cols = i;
406                         i = 0;
407                 }
408         }
409
410         i = 0;
411         for(t = h; t; t = t->next){
412                 i++;
413                 if(t->brk){
414                         while(i < cols){
415                                 s = mallocz(sizeof(Table), 1);
416                                 strcpy(s->fmt, "L");
417                                 s->brk = t->brk;
418                                 t->brk = 0;
419                                 s->next = t->next;
420                                 t->next = s;
421                                 i++;
422                         }
423                         break;
424                 }
425         }
426
427         s = h;
428         while(s){
429                 emit(text, ".TS\n");
430                 if(gotattr(tag, "align", "center"))
431                         emit(text, "center ;\n");
432                 i = 0;
433                 for(t = s; t; t = t->next){
434                         emit(text, "%s", t->fmt);
435                         if(t->brk){
436                                 emitrune(text, '\n');
437                                 if(++i > 30){
438                                         t = t->next;
439                                         break;
440                                 }
441                         }else
442                                 emitrune(text, ' ');
443                 }
444                 emit(text, ".\n");
445                 dumprows(text, s, t);
446                 emit(text, ".TE\n");
447                 s = t;
448         }
449
450         while(t = h){
451                 h = t->next;
452                 free(t->bp);
453                 free(t);
454         }
455 }
456
457 void
458 ontable(Text *, Tag *tag)
459 {
460         tag->aux = nil;
461         tag->close = endtable;
462 }
463
464 void
465 endcell(Text *text, Tag *tag)
466 {
467         Table *t;
468         Tag *tt;
469         int i;
470
471         if((tt = tabletag(tag)) == nil)
472                 return;
473         if(cistrcmp(tag->tag, "tr") == 0){
474                 if(t = tt->aux)
475                         t->brk = 1;
476         } else {
477                 t = tag->aux;
478                 t->bp = text->bp;
479                 t->nb = text->wp - text->bp;
480
481                 for(i=0; i<t->nb; i++)
482                         if(strchr(" \t\r\n", t->bp[i]) == nil)
483                                 break;
484                 if(i > 0){
485                         memmove(t->bp, t->bp+i, t->nb - i);
486                         t->nb -= i;
487                 }
488                 while(t->nb > 0 && strchr(" \t\r\n", t->bp[t->nb-1]))
489                         t->nb--;
490                 if(t->nb < 32){
491                         for(i=0; i<t->nb; i++)
492                                 if(strchr("\t\r\n", t->bp[i]))
493                                         break;
494                         t->enclose = i < t->nb;
495                 } else {
496                         t->enclose = 1;
497                 }
498                 if(gotstyle(tag, "text-align", "center") || gotstyle(tt, "text-align", "center"))
499                         strcpy(t->fmt, "C");
500                 else
501                         strcpy(t->fmt, "L");
502                 if(strcmp(tag->tag, "th") == 0)
503                         strcpy(t->fmt+1, "B");
504                 t->prev = tt->aux;
505                 tt->aux = t;
506                 *text = t->save;
507         }
508 }
509
510 void
511 oncell(Text *text, Tag *tag)
512 {
513         Tag *tt;
514
515         if((tt = tabletag(tag)) == nil)
516                 return;
517         if(cistrcmp(tag->tag, "tr")){
518                 Table *t;
519
520                 tt = tag->up;
521                 while(tt && cistrcmp(tt->tag, "tr"))
522                         tt = tt->up;
523                 if(tt == nil)
524                         return;
525                 reparent(text, tag, tt);
526
527                 t = mallocz(sizeof(*t), 1);
528                 t->save = *text;
529                 tag->aux = t;
530
531                 text->bp = nil;
532                 text->wp = nil;
533                 text->nb = 0;
534                 text->pos = 0;
535                 text->space = 0;
536         } else
537                 reparent(text, tag, tt);
538         tag->close = endcell;
539 }
540
541 struct {
542         char    *tag;
543         void    (*open)(Text *, Tag *);
544 } ontag[] = {
545         "b",            onb,
546         "big",          onbig,
547         "blockquote",   onquote,
548         "br",           onbr,
549         "cite",         oni,
550         "code",         ontt,
551         "dfn",          oni,
552         "em",           oni,
553         "h1",           onh,
554         "h2",           onh,
555         "h3",           onh,
556         "h4",           onh,
557         "h5",           onh,
558         "h6",           onh,
559         "head",         ongarbage,
560         "hr",           onbr,
561         "i",            oni,
562         "img",          onmeta,
563         "kbd",          ontt,
564         "li",           onli,
565         "link",         onmeta,
566         "meta",         onmeta,
567         "p",            onp,
568         "pre",          onpre,
569         "q",            onquote,
570         "samp",         ontt,
571         "script",       ongarbage,
572         "small",        onsmall,
573         "strong",       onb,
574         "style",        ongarbage,
575         "table",        ontable,
576         "td",           oncell,
577         "th",           oncell,
578         "tr",           oncell,
579         "sub",          onsub,
580         "sup",          onsup,
581         "span",         onspan,
582         "tt",           ontt,
583         "var",          oni,
584 };
585
586 void
587 eatwhite(void)
588 {
589         int c;
590
591         while((c = Bgetc(&in)) > 0){
592                 if(strchr("\n\r\t ", c) == nil){
593                         Bungetc(&in);
594                         return;
595                 }
596         }
597 }
598
599 void
600 parsecomment(void)
601 {
602         char buf[64];
603         int n, c;
604
605         n = 0;
606         eatwhite();
607         while((c = Bgetc(&in)) > 0){
608                 if(c == '>')
609                         return;
610                 if(n == 0 && c == '-'){
611                         while((c = Bgetc(&in)) > 0){
612                                 if(c == '-')
613                                         if(Bgetc(&in) == '-')
614                                                 if(Bgetc(&in) == '>')
615                                                         return;
616                         }
617                 }
618                 if(n+1 < sizeof(buf)){
619                         buf[n++] = c;
620                         if(n != 7 || cistrncmp(buf, "[CDATA[", 7))
621                                 continue;
622                         while((c = Bgetc(&in)) > 0){
623                                 if(c == ']'){
624                                         if(Bgetc(&in) == ']'){
625                                                 if(Bgetc(&in) != '>')
626                                                         Bungetc(&in);
627                                                 return;
628                                         }
629                                 }
630                         }
631                 }
632         }
633 }
634
635 int
636 parseattr(Attr *a)
637 {
638         int q, c, n;
639
640         n = 0;
641         eatwhite();
642         while((c = Bgetc(&in)) > 0){
643                 if(strchr("</>=?!", c)){
644                         Bungetc(&in);
645                         break;
646                 }
647                 if(strchr("\n\r\t ", c))
648                         break;
649                 if(n < sizeof(a->attr)-1)
650                         a->attr[n++] = c;
651         }
652         if(n == 0)
653                 return 0;
654         a->attr[n] = 0;
655         n = 0;
656         eatwhite();
657         if(Bgetc(&in) == '='){
658                 eatwhite();
659                 c = Bgetc(&in);
660                 if(strchr("'\"", c)){
661                         q = c;
662                         while((c = Bgetc(&in)) > 0){
663                                 if(c == q)
664                                         break;
665                                 if(n < sizeof(a->val)-1)
666                                         a->val[n++] = c;
667                         }
668                 } else {
669                         Bungetc(&in);
670                         while((c = Bgetc(&in)) > 0){
671                                 if(strchr("\n\r\t </>?!", c)){
672                                         Bungetc(&in);
673                                         break;
674                                 }
675                                 if(n < sizeof(a->val)-1)
676                                         a->val[n++] = c;
677                         }
678                 }
679         } else
680                 Bungetc(&in);
681         a->val[n] = 0;
682         return 1;
683 }
684
685 int
686 parsetag(Tag *t)
687 {
688         int n, c;
689
690         t->nattr = 0;
691         t->opening = 1;
692         t->closing = 0;
693
694         n = 0;
695         eatwhite();
696         while((c = Bgetc(&in)) > 0){
697                 if(c == '>')
698                         break;
699                 if(strchr("\n\r\t ", c)){
700                         if(parseattr(t->attr + t->nattr))
701                                 if(t->nattr < nelem(t->attr)-1)
702                                         t->nattr++;
703                         continue;
704                 }
705                 if(n == 0 && strchr("?!", c)){
706                         parsecomment();
707                         return 0;
708                 }
709                 if(c == '/'){
710                         if(n == 0){
711                                 t->opening = 0;
712                                 t->closing = 1;
713                         } else
714                                 t->closing = 1;
715                         continue;
716                 }
717                 if(n < sizeof(t->tag)-1)
718                         t->tag[n++] = c;
719         }
720         t->tag[n] = 0;
721         return n > 0;
722 }
723
724 Rune
725 parserune(int c)
726 {
727         char buf[10];
728         int n;
729         Rune r;
730
731         n = 0;
732         if(c == '&'){
733                 while((c = Bgetc(&in)) > 0){
734                         if(strchr(";&</>\n\r\t ", c)){
735                                 if(c != ';')
736                                         Bungetc(&in);
737                                 if(n == 0)
738                                         return '&';
739                                 break;
740                         }
741                         if(n == sizeof(buf)-1)
742                                 break;
743                         buf[n++] = c;
744                 }
745                 buf[n] = 0;
746                 if(strcmp(buf, "lt") == 0)
747                         return '<';
748                 if(strcmp(buf, "gt") == 0)
749                         return '>';
750                 if(strcmp(buf, "quot") == 0)
751                         return '"';
752                 if(strcmp(buf, "apos") == 0)
753                         return '\'';
754                 if(strcmp(buf, "amp") == 0)
755                         return '&';
756                 /* use tcs -f html to handle the rest. */
757         } else {
758                 do {
759                         buf[n++] = c;
760                         if(fullrune(buf, n)){
761                                 chartorune(&r, buf);
762                                 return r;
763                         }
764                         if(n >= UTFmax)
765                                 break;
766                 } while((c = Bgetc(&in)) > 0);
767         }
768         return Runeerror;
769 }
770
771 Rune
772 substrune(Rune r)
773 {
774         switch(r){
775         case 0x2019:
776         case 0x2018:
777                 return '\'';
778         case 0x201c:
779         case 0x201d:
780                 return '"';
781         default:
782                 return r;
783         }
784 }
785
786 void
787 debugtag(Tag *tag, char *dbg)
788 {
789         if(1){
790                 USED(tag);
791                 USED(dbg);
792                 return;
793         }
794
795         if(tag == nil)
796                 return;
797         debugtag(tag->up, nil);
798         fprint(2, "%s %s%s", tag->tag, dbg ? dbg : " > ", dbg ? "\n" : "");
799 }
800
801 char*
802 getattr(Tag *tag, char *attr)
803 {
804         int i;
805
806         for(i=0; i<tag->nattr; i++)
807                 if(cistrcmp(tag->attr[i].attr, attr) == 0)
808                         return tag->attr[i].val;
809         return nil;
810 }
811
812 int
813 gotattr(Tag *tag, char *attr, char *val)
814 {
815         char *v;
816
817         if((v = getattr(tag, attr)) == nil)
818                 return 0;
819         return cistrstr(v, val) != 0;
820 }
821
822 int
823 gotstyle(Tag *tag, char *style, char *val)
824 {
825         char *v;
826
827         if((v = getattr(tag, "style")) == nil)
828                 return 0;
829         if((v = cistrstr(v, style)) == nil)
830                 return 0;
831         v += strlen(style);
832         while(*v && *v != ':')
833                 v++;
834         if(*v != ':')
835                 return 0;
836         v++;
837         while(*v && strchr("\t ", *v))
838                 v++;
839         if(cistrncmp(v, val, strlen(val)))
840                 return 0;
841         return 1;
842 }
843
844 void
845 reparent(Text *text, Tag *tag, Tag *up)
846 {
847         Tag *old;
848
849         old = tag->up;
850         while(old != up){
851                 debugtag(old, "reparent");
852                 if(old->close){
853                         old->close(text, old);
854                         old->close = nil;
855                 }
856                 old = old->up;
857         }
858         tag->up = up;
859 }
860
861
862 void
863 parsetext(Text *text, Tag *tag)
864 {
865         int hidden, c;
866         Tag t, *up;
867         Rune r;
868
869         if(tag){
870                 up = tag->up;
871                 debugtag(tag, "open");
872                 for(c = 0; c < nelem(ontag); c++){
873                         if(cistrcmp(tag->tag, ontag[c].tag) == 0){
874                                 ontag[c].open(text, tag);
875                                 break;
876                         }
877                 }
878                 hidden = getattr(tag, "hidden") || gotstyle(tag, "display", "none");
879         } else {
880                 up = nil;
881                 hidden = 0;
882         }
883         if(tag == nil || tag->closing == 0){
884                 while((c = Bgetc(&in)) > 0){
885                         if(c == '<'){
886                                 memset(&t, 0, sizeof(t));
887                                 if(parsetag(&t)){
888                                         if(t.opening){
889                                                 t.up = tag;
890                                                 parsetext(text, &t);
891                                                 if(t.up != tag){
892                                                         debugtag(tag, "skip");
893                                                         up = t.up;
894                                                         break;
895                                                 }
896                                                 debugtag(tag, "back");
897                                         } else if(t.closing){
898                                                 up = tag;
899                                                 while(up && cistrcmp(up->tag, t.tag))
900                                                         up = up->up;
901                                                 if(up){
902                                                         up = up->up;
903                                                         break;
904                                                 }
905                                         }
906                                 }
907                                 continue;
908                         }
909                         if(hidden || !text->output)
910                                 continue;
911                         r = substrune(parserune(c));
912                         switch(r){
913                         case '\n':
914                         case '\r':
915                         case ' ':
916                         case '\t':
917                                 if(text->pre == 0){
918                                         text->space = 1;
919                                         break;
920                                 }
921                         default:
922                                 if(text->space){
923                                         if(text->pos >= 70)
924                                                 emitrune(text, '\n');
925                                         else if(text->pos > 0)
926                                                 emitrune(text, ' ');
927                                 }
928                                 if((text->pos == 0 && r == '.') || r == '\\')
929                                         emit(text, "\\&");
930                                 if(r == '\\' || r == 0xA0)
931                                         emitrune(text, '\\');
932                                 if(r == 0xA0)
933                                         r = ' ';
934                                 emitrune(text, r);
935                                 text->space = 0;
936                         }
937                 }
938         }
939         if(tag){
940                 debugtag(tag, "close");
941                 if(tag->close){
942                         tag->close(text, tag);
943                         tag->close = nil;
944                 }
945                 if(up)
946                         tag->up = up;
947         }
948 }
949
950 void
951 inittext(Text *text)
952 {
953         memset(text, 0, sizeof(Text));
954         text->fontstyle = "R";
955         text->fontsize = "NL";
956         text->output = 1;
957 }
958
959 void
960 main(void)
961 {
962         Text text;
963         Binit(&in, 0, OREAD);
964         inittext(&text);
965         parsetext(&text, nil);
966         emit(&text, "\n");
967         write(1, text.bp, text.wp - text.bp);
968 }