Encode metadata in the documentation source, rather than hard-coding it.
[platform/upstream/nasm.git] / doc / rdsrc.pl
1 #!/usr/bin/perl
2
3 # Read the source-form of the NASM manual and generate the various
4 # output forms.
5
6 # TODO:
7 #
8 # Ellipsis support would be nice.
9
10 # Source-form features:
11 # ---------------------
12
13 # Bullet \b
14 #   Bullets the paragraph. Rest of paragraph is indented to cope. In
15 #   HTML, consecutive groups of bulleted paragraphs become unordered
16 #   lists.
17
18 # Emphasis \e{foobar}
19 #   produces `_foobar_' in text and italics in HTML, PS, RTF
20
21 # Inline code \c{foobar}
22 #   produces ``foobar'' in text, and fixed-pitch font in HTML, PS, RTF
23
24 # Display code
25 # \c  line one
26 # \c   line two
27 #   produces fixed-pitch font where appropriate, and doesn't break
28 #   pages except sufficiently far into the middle of a display.
29
30 # Chapter, header and subheader
31 # \C{intro} Introduction
32 # \H{whatsnasm} What is NASM?
33 # \S{free} NASM Is Free
34 #   dealt with as appropriate. Chapters begin on new sides, possibly
35 #   even new _pages_. (Sub)?headers are good places to begin new
36 #   pages. Just _after_ a (sub)?header isn't.
37 #   The keywords can be substituted with \K and \k.
38 #
39 # Keyword \K{cintro} \k{cintro}
40 #   Expands to `Chapter 1', `Section 1.1', `Section 1.1.1'. \K has an
41 #   initial capital whereas \k doesn't. In HTML, will produce
42 #   hyperlinks.
43
44 # Web link \W{http://foobar/}{text} or \W{mailto:me@here}\c{me@here}
45 #   the \W prefix is ignored except in HTML; in HTML the last part
46 #   becomes a hyperlink to the first part.
47
48 # Literals \{ \} \\
49 #   In case it's necessary, they expand to the real versions.
50
51 # Nonbreaking hyphen \-
52 #   Need more be said?
53
54 # Source comment \#
55 #   Causes everything after it on the line to be ignored by the
56 #   source-form processor.
57 #
58 # Indexable word \i{foobar} (or \i\e{foobar} or \i\c{foobar}, equally)
59 #   makes word appear in index, referenced to that point
60 #   \i\c comes up in code style even in the index; \i\e doesn't come
61 #   up in emphasised style.
62 #
63 # Indexable non-displayed word \I{foobar} or \I\c{foobar}
64 #   just as \i{foobar} except that nothing is displayed for it
65 #
66 # Index rewrite
67 # \IR{foobar} \c{foobar} operator, uses of
68 #   tidies up the appearance in the index of something the \i or \I
69 #   operator was applied to
70 #
71 # Index alias
72 # \IA{foobar}{bazquux}
73 #   aliases one index tag (as might be supplied to \i or \I) to
74 #   another, so that \I{foobar} has the effect of \I{bazquux}, and
75 #   \i{foobar} has the effect of \I{bazquux}foobar
76 #
77 # Metadata
78 # \M{key}{something}
79 #   defines document metadata, such as authorship, title and copyright;
80 #   different output formats use this differently.
81 #
82
83 $diag = 1, shift @ARGV if $ARGV[0] eq "-d";
84
85 $| = 1;
86
87 $tstruct_previtem = $node = "Top";
88 $nodes = ($node);
89 $tstruct_level{$tstruct_previtem} = 0;
90 $tstruct_last[$tstruct_level{$tstruct_previtem}] = $tstruct_previtem;
91 $MAXLEVEL = 10;  # really 3, but play safe ;-)
92
93 # Read the file; pass a paragraph at a time to the paragraph processor.
94 print "Reading input...";
95 $pname = "para000000";
96 @pnames = @pflags = ();
97 $para = undef;
98 while (<>) {
99   chomp;
100   if (!/\S/ || /^\\(IA|IR|M)/) { # special case: \IA \IR \M imply new-paragraph
101     &got_para($para);
102     $para = undef;
103   }
104   if (/\S/) {
105     s/\\#.*$//; # strip comments
106     $para .= " " . $_;
107   }
108 }
109 &got_para($para);
110 print "done.\n";
111
112 # Now we've read in the entire document and we know what all the
113 # heading keywords refer to. Go through and fix up the \k references.
114 print "Fixing up cross-references...";
115 &fixup_xrefs;
116 print "done.\n";
117
118 # Sort the index tags, according to the slightly odd order I've decided on.
119 print "Sorting index tags...";
120 &indexsort;
121 print "done.\n";
122
123 if ($diag) {
124   print "Writing index-diagnostic file...";
125   &indexdiag;
126   print "done.\n";
127 }
128
129 # OK. Write out the various output files.
130 print "Producing text output: ";
131 &write_txt;
132 print "done.\n";
133 print "Producing HTML output: ";
134 &write_html;
135 print "done.\n";
136 print "Producing PostScript output: ";
137 &write_ps;
138 print "done.\n";
139 print "Producing Texinfo output: ";
140 &write_texi;
141 print "done.\n";
142 print "Producing WinHelp output: ";
143 &write_hlp;
144 print "done.\n";
145
146 sub got_para {
147   local ($_) = @_;
148   my $pflags = "", $i, $w, $l, $t;
149   return if !/\S/;
150
151   @$pname = ();
152
153   # Strip off _leading_ spaces, then determine type of paragraph.
154   s/^\s*//;
155   $irewrite = undef;
156   if (/^\\c[^{]/) {
157     # A code paragraph. The paragraph-array will contain the simple
158     # strings which form each line of the paragraph.
159     $pflags = "code";
160     while (/^\\c (([^\\]|\\[^c])*)(.*)$/) {
161       $l = $1;
162       $_ = $3;
163       $l =~ s/\\{/{/g;
164       $l =~ s/\\}/}/g;
165       $l =~ s/\\\\/\\/g;
166       push @$pname, $l;
167     }
168     $_ = ''; # suppress word-by-word code
169   } elsif (/^\\C/) {
170     # A chapter heading. Define the keyword and allocate a chapter
171     # number.
172     $cnum++;
173     $hnum = 0;
174     $snum = 0;
175     $xref = "chapter-$cnum";
176     $pflags = "chap $cnum :$xref";
177     die "badly formatted chapter heading: $_\n" if !/^\\C{([^}]*)}\s*(.*)$/;
178     $refs{$1} = "chapter $cnum";
179     $node = "Chapter $cnum";
180     &add_item($node, 1);
181     $xrefnodes{$node} = $xref; $nodexrefs{$xref} = $node;
182     $xrefs{$1} = $xref;
183     $_ = $2;
184     # the standard word-by-word code will happen next
185   } elsif (/^\\A/) {
186     # An appendix heading. Define the keyword and allocate an appendix
187     # letter.
188     $cnum++;
189     $cnum = 'A' if $cnum =~ /[0-9]+/;
190     $hnum = 0;
191     $snum = 0;
192     $xref = "appendix-$cnum";
193     $pflags = "appn $cnum :$xref";
194     die "badly formatted appendix heading: $_\n" if !/^\\A{([^}]*)}\s*(.*)$/;
195     $refs{$1} = "appendix $cnum";
196     $node = "Appendix $cnum";
197     &add_item($node, 1);
198     $xrefnodes{$node} = $xref; $nodexrefs{$xref} = $node;
199     $xrefs{$1} = $xref;
200     $_ = $2;
201     # the standard word-by-word code will happen next
202   } elsif (/^\\H/) {
203     # A major heading. Define the keyword and allocate a section number.
204     $hnum++;
205     $snum = 0;
206     $xref = "section-$cnum.$hnum";
207     $pflags = "head $cnum.$hnum :$xref";
208     die "badly formatted heading: $_\n" if !/^\\[HP]{([^}]*)}\s*(.*)$/;
209     $refs{$1} = "section $cnum.$hnum";
210     $node = "Section $cnum.$hnum";
211     &add_item($node, 2);
212     $xrefnodes{$node} = $xref; $nodexrefs{$xref} = $node;
213     $xrefs{$1} = $xref;
214     $_ = $2;
215     # the standard word-by-word code will happen next
216   } elsif (/^\\S/) {
217     # A sub-heading. Define the keyword and allocate a section number.
218     $snum++;
219     $xref = "section-$cnum.$hnum.$snum";
220     $pflags = "subh $cnum.$hnum.$snum :$xref";
221     die "badly formatted subheading: $_\n" if !/^\\S{([^}]*)}\s*(.*)$/;
222     $refs{$1} = "section $cnum.$hnum.$snum";
223     $node = "Section $cnum.$hnum.$snum";
224     &add_item($node, 3);
225     $xrefnodes{$node} = $xref; $nodexrefs{$xref} = $node;
226     $xrefs{$1} = $xref;
227     $_ = $2;
228     # the standard word-by-word code will happen next
229   } elsif (/^\\IR/) {
230     # An index-rewrite.
231     die "badly formatted index rewrite: $_\n" if !/^\\IR{([^}]*)}\s*(.*)$/;
232     $irewrite = $1;
233     $_ = $2;
234     # the standard word-by-word code will happen next
235   } elsif (/^\\IA/) {
236     # An index-alias.
237     die "badly formatted index alias: $_\n" if !/^\\IA{([^}]*)}{([^}]*)}\s*$/;
238     $idxalias{$1} = $2;
239     return; # avoid word-by-word code
240   } elsif (/^\\M/) {
241     # Metadata
242     die "badly formed metadata: $_\n" if !/^\\M{([^}]*)}{([^}]*)}\s*$/;
243     $metadata{$1} = $2;
244     return; # avoid word-by-word code
245   } elsif (/^\\b/) {
246     # A bulleted paragraph. Strip off the initial \b and let the
247     # word-by-word code take care of the rest.
248     $pflags = "bull";
249     s/^\\b\s*//;
250   } else {
251     # A normal paragraph. Just set $pflags: the word-by-word code does
252     # the rest.
253     $pflags = "norm";
254   }
255
256   # The word-by-word code: unless @$pname is already defined (which it
257   # will be in the case of a code paragraph), split the paragraph up
258   # into words and push each on @$pname.
259   #
260   # Each thing pushed on @$pname should have a two-character type
261   # code followed by the text.
262   #
263   # Type codes are:
264   # "n " for normal
265   # "da" for a dash
266   # "es" for first emphasised word in emphasised bit
267   # "e " for emphasised in mid-emphasised-bit
268   # "ee" for last emphasised word in emphasised bit
269   # "eo" for single (only) emphasised word
270   # "c " for code
271   # "k " for cross-ref
272   # "kK" for capitalised cross-ref
273   # "w " for Web link
274   # "wc" for code-type Web link
275   # "x " for beginning of resolved cross-ref; generates no visible output,
276   #      and the text is the cross-reference code
277   # "xe" for end of resolved cross-ref; text is same as for "x ".
278   # "i " for point to be indexed: the text is the internal index into the
279   #      index-items arrays
280   # "sp" for space
281   while (/\S/) {
282     s/^\s*//, push @$pname, "sp" if /^\s/;
283     $indexing = $qindex = 0;
284     if (/^(\\[iI])?\\c/) {
285       $qindex = 1 if $1 eq "\\I";
286       $indexing = 1, s/^\\[iI]// if $1;
287       s/^\\c//;
288       die "badly formatted \\c: \\c$_\n" if !/{(([^\\}]|\\.)*)}(.*)$/;
289       $w = $1;
290       $_ = $3;
291       $w =~ s/\\{/{/g;
292       $w =~ s/\\}/}/g;
293       $w =~ s/\\-/-/g;
294       $w =~ s/\\\\/\\/g;
295       (push @$pname,"i"),$lastp = $#$pname if $indexing;
296       push @$pname,"c $w" if !$qindex;
297       $$pname[$lastp] = &addidx($node, $w, "c $w") if $indexing;
298     } elsif (/^\\[iIe]/) {
299       /^(\\[iI])?(\\e)?/;
300       $emph = 0;
301       $qindex = 1 if $1 eq "\\I";
302       $indexing = 1, $type = "\\i" if $1;
303       $emph = 1, $type = "\\e" if $2;
304       s/^(\\[iI])?(\\e?)//;
305       die "badly formatted $type: $type$_\n" if !/{(([^\\}]|\\.)*)}(.*)$/;
306       $w = $1;
307       $_ = $3;
308       $w =~ s/\\{/{/g;
309       $w =~ s/\\}/}/g;
310       $w =~ s/\\-/-/g;
311       $w =~ s/\\\\/\\/g;
312       $t = $emph ? "es" : "n ";
313       @ientry = ();
314       (push @$pname,"i"),$lastp = $#$pname if $indexing;
315       foreach $i (split /\s+/,$w) {  # \e and \i can be multiple words
316         push @$pname,"$t$i","sp" if !$qindex;
317         ($ii=$i) =~ tr/A-Z/a-z/, push @ientry,"n $ii","sp" if $indexing;
318         $t = $emph ? "e " : "n ";
319       }
320       $w =~ tr/A-Z/a-z/, pop @ientry if $indexing;
321       $$pname[$lastp] = &addidx($node, $w, @ientry) if $indexing;
322       pop @$pname if !$qindex; # remove final space
323       if (substr($$pname[$#$pname],0,2) eq "es" && !$qindex) {
324         substr($$pname[$#$pname],0,2) = "eo";
325       } elsif ($emph && !$qindex) {
326         substr($$pname[$#$pname],0,2) = "ee";
327       }
328     } elsif (/^\\[kK]/) {
329       $t = "k ";
330       $t = "kK" if /^\\K/;
331       s/^\\[kK]//;
332       die "badly formatted \\k: \\c$_\n" if !/{([^}]*)}(.*)$/;
333       $_ = $2;
334       push @$pname,"$t$1";
335     } elsif (/^\\W/) {
336       s/^\\W//;
337       die "badly formatted \\W: \\W$_\n"
338           if !/{([^}]*)}(\\i)?(\\c)?{(([^\\}]|\\.)*)}(.*)$/;
339       $l = $1;
340       $w = $4;
341       $_ = $6;
342       $t = "w ";
343       $t = "wc" if $3 eq "\\c";
344       $indexing = 1 if $2;
345       $w =~ s/\\{/{/g;
346       $w =~ s/\\}/}/g;
347       $w =~ s/\\-/-/g;
348       $w =~ s/\\\\/\\/g;
349       (push @$pname,"i"),$lastp = $#$pname if $indexing;
350       push @$pname,"$t<$l>$w";
351       $$pname[$lastp] = &addidx($node, $w, "c $w") if $indexing;
352     } else {
353       die "what the hell? $_\n" if !/^(([^\s\\\-]|\\[\\{}\-])*-?)(.*)$/;
354       die "painful death! $_\n" if !length $1;
355       $w = $1;
356       $_ = $3;
357       $w =~ s/\\{/{/g;
358       $w =~ s/\\}/}/g;
359       $w =~ s/\\-/-/g;
360       $w =~ s/\\\\/\\/g;
361       if ($w eq "-") {
362         push @$pname,"da";
363       } else {
364         push @$pname,"n $w";
365       }
366     }
367   }
368   if ($irewrite ne undef) {
369     &addidx(undef, $irewrite, @$pname);
370     @$pname = ();
371   } else {
372     push @pnames, $pname;
373     push @pflags, $pflags;
374     $pname++;
375   }
376 }
377
378 sub addidx {
379   my ($node, $text, @ientry) = @_;
380   $text = $idxalias{$text} || $text;
381   if ($node eq undef || !$idxmap{$text}) {
382     @$ientry = @ientry;
383     $idxmap{$text} = $ientry;
384     $ientry++;
385   }
386   if ($node) {
387     $idxnodes{$node,$text} = 1;
388     return "i $text";
389   }
390 }
391
392 sub indexsort {
393   my $iitem, $ientry, $i, $piitem, $pcval, $cval, $clrcval;
394
395   @itags = map { # get back the original data as the 1st elt of each list
396              $_->[0]
397            } sort { # compare auxiliary (non-first) elements of lists
398              $a->[1] cmp $b->[1] ||
399              $a->[2] cmp $b->[2] ||
400              $a->[0] cmp $b->[0]
401            } map { # transform array into list of 3-element lists
402              my $ientry = $idxmap{$_};
403              my $a = substr($$ientry[0],2);
404              $a =~ tr/A-Za-z//cd;
405              [$_, uc($a), substr($$ientry[0],0,2)]
406            } keys %idxmap;
407
408   # Having done that, check for comma-hood.
409   $cval = 0;
410   foreach $iitem (@itags) {
411     $ientry = $idxmap{$iitem};
412     $clrcval = 1;
413     $pcval = $cval;
414     FL:for ($i=0; $i <= $#$ientry; $i++) {
415       if ($$ientry[$i] =~ /^(n .*,)(.*)/) {
416         $$ientry[$i] = $1;
417         splice @$ientry,$i+1,0,"n $2" if length $2;
418         $commapos{$iitem} = $i+1;
419         $cval = join("\002", @$ientry[0..$i]);
420         $clrcval = 0;
421         last FL;
422       }
423     }
424     $cval = undef if $clrcval;
425     $commanext{$iitem} = $commaafter{$piitem} = 1
426       if $cval and ($cval eq $pcval);
427     $piitem = $iitem;
428   }
429 }
430
431 sub indexdiag {
432   my $iitem,$ientry,$w,$ww,$foo,$node;
433   open INDEXDIAG,">index.diag";
434   foreach $iitem (@itags) {
435     $ientry = $idxmap{$iitem};
436     print INDEXDIAG "<$iitem> ";
437     foreach $w (@$ientry) {
438       $ww = &word_txt($w);
439       print INDEXDIAG $ww unless $ww eq "\001";
440     }
441     print INDEXDIAG ":";
442     $foo = " ";
443     foreach $node (@nodes) {
444       (print INDEXDIAG $foo,$node), $foo = ", " if $idxnodes{$node,$iitem};
445     }
446     print INDEXDIAG "\n";
447   }
448   close INDEXDIAG;
449 }
450
451 sub fixup_xrefs {
452   my $pname, $p, $i, $j, $k, $caps, @repl;
453
454   for ($p=0; $p<=$#pnames; $p++) {
455     next if $pflags[$p] eq "code";
456     $pname = $pnames[$p];
457     for ($i=$#$pname; $i >= 0; $i--) {
458       if ($$pname[$i] =~ /^k/) {
459         $k = $$pname[$i];
460         $caps = ($k =~ /^kK/);
461         $k = substr($k,2);      
462         $repl = $refs{$k};
463         die "undefined keyword `$k'\n" unless $repl;
464         substr($repl,0,1) =~ tr/a-z/A-Z/ if $caps;
465         @repl = ();
466         push @repl,"x $xrefs{$k}";
467         foreach $j (split /\s+/,$repl) {
468           push @repl,"n $j";
469           push @repl,"sp";
470         }
471         pop @repl; # remove final space
472         push @repl,"xe$xrefs{$k}";
473         splice @$pname,$i,1,@repl;
474       }
475     }
476   }
477 }
478
479 sub write_txt {
480   # This is called from the top level, so I won't bother using
481   # my or local.
482
483   # Open file.
484   print "writing file...";
485   open TEXT,">nasmdoc.txt";
486   select TEXT;
487
488   # Preamble.
489   $title = "The Netwide Assembler: NASM";
490   $spaces = ' ' x ((75-(length $title))/2);
491   ($underscore = $title) =~ s/./=/g;
492   print "$spaces$title\n$spaces$underscore\n";
493
494   for ($para = 0; $para <= $#pnames; $para++) {
495     $pname = $pnames[$para];
496     $pflags = $pflags[$para];
497     $ptype = substr($pflags,0,4);
498
499     print "\n"; # always one of these before a new paragraph
500
501     if ($ptype eq "chap") {
502       # Chapter heading. "Chapter N: Title" followed by a line of
503       # minus signs.
504       $pflags =~ /chap (.*) :(.*)/;
505       $title = "Chapter $1: ";
506       foreach $i (@$pname) {
507         $ww = &word_txt($i);
508         $title .= $ww unless $ww eq "\001";
509       }
510       print "$title\n";
511       $title =~ s/./-/g;
512       print "$title\n";
513     } elsif ($ptype eq "appn") {
514       # Appendix heading. "Appendix N: Title" followed by a line of
515       # minus signs.
516       $pflags =~ /appn (.*) :(.*)/;
517       $title = "Appendix $1: ";
518       foreach $i (@$pname) {
519         $ww = &word_txt($i);
520         $title .= $ww unless $ww eq "\001";
521       }
522       print "$title\n";
523       $title =~ s/./-/g;
524       print "$title\n";
525     } elsif ($ptype eq "head" || $ptype eq "subh") {
526       # Heading or subheading. Just a number and some text.
527       $pflags =~ /.... (.*) :(.*)/;
528       $title = sprintf "%6s ", $1;
529       foreach $i (@$pname) {
530         $ww = &word_txt($i);
531         $title .= $ww unless $ww eq "\001";
532       }
533       print "$title\n";
534     } elsif ($ptype eq "code") {
535       # Code paragraph. Emit each line with a seven character indent.
536       foreach $i (@$pname) {
537         warn "code line longer than 68 chars: $i\n" if length $i > 68;
538         print ' 'x7, $i, "\n";
539       }
540     } elsif ($ptype eq "bull" || $ptype eq "norm") {
541       # Ordinary paragraph, optionally bulleted. We wrap, with ragged
542       # 75-char right margin and either 7 or 11 char left margin
543       # depending on bullets.
544       if ($ptype eq "bull") {
545         $line = ' 'x7 . '(*) ';
546         $next = ' 'x11;
547       } else {
548         $line = $next = ' 'x7;
549       }
550       @a = @$pname;
551       $wd = $wprev = '';
552       do {
553         do { $w = &word_txt(shift @a) } while $w eq "\001"; # nasty hack
554         $wd .= $wprev;
555         if ($wprev =~ /-$/ || $w eq ' ' || $w eq '' || $w eq undef) {
556           if (length ($line . $wd) > 75) {
557             $line =~ s/\s*$//; # trim trailing spaces
558             print "$line\n";
559             $line = $next;
560             $wd =~ s/^\s*//; # trim leading spaces
561           }
562           $line .= $wd;
563           $wd = '';
564         }
565         $wprev = $w;
566       } while ($w ne '' && $w ne undef);
567       if ($line =~ /\S/) {
568         $line =~ s/\s*$//; # trim trailing spaces
569         print "$line\n";
570       }
571     }
572   }
573
574   # Close file.
575   select STDOUT;
576   close TEXT;
577 }
578
579 sub word_txt {
580   my ($w) = @_;
581   my $wtype, $wmajt;
582
583   return undef if $w eq '' || $w eq undef;
584   $wtype = substr($w,0,2);
585   $wmajt = substr($wtype,0,1);
586   $w = substr($w,2);
587   $w =~ s/<.*>// if $wmajt eq "w"; # remove web links
588   if ($wmajt eq "n" || $wtype eq "e " || $wtype eq "w ") {
589     return $w;
590   } elsif ($wtype eq "sp") {
591     return ' ';
592   } elsif ($wtype eq "da") {
593     return '-';
594   } elsif ($wmajt eq "c" || $wtype eq "wc") {
595     return "`${w}'";
596   } elsif ($wtype eq "es") {
597     return "_${w}";
598   } elsif ($wtype eq "ee") {
599     return "${w}_";
600   } elsif ($wtype eq "eo") {
601     return "_${w}_";
602   } elsif ($wmajt eq "x" || $wmajt eq "i") {
603     return "\001";
604   } else {
605     die "panic in word_txt: $wtype$w\n";
606   }
607 }
608
609 sub write_html {
610   # This is called from the top level, so I won't bother using
611   # my or local.
612
613   # Write contents file. Just the preamble, then a menu of links to the
614   # separate chapter files and the nodes therein.
615   print "writing contents file...";
616   open TEXT,">nasmdoc0.html";
617   select TEXT;
618   &html_preamble(0);
619   print "<p>This manual documents NASM, the Netwide Assembler: an assembler\n";
620   print "targetting the Intel x86 series of processors, with portable source.\n";
621   print "<p>";
622   for ($node = $tstruct_next{'Top'}; $node; $node = $tstruct_next{$node}) {
623     if ($tstruct_level{$node} == 1) {
624       # Invent a file name.
625       ($number = lc($xrefnodes{$node})) =~ s/.*-//;
626       $fname="nasmdocx.html";
627       substr($fname,8 - length $number, length $number) = $number;
628       $html_fnames{$node} = $fname;
629       $link = $fname;
630       print "<p>";
631     } else {
632       # Use the preceding filename plus a marker point.
633       $link = $fname . "#$xrefnodes{$node}";
634     }
635     $title = "$node: ";
636     $pname = $tstruct_pname{$node};
637     foreach $i (@$pname) {
638       $ww = &word_html($i);
639       $title .= $ww unless $ww eq "\001";
640     }
641     print "<a href=\"$link\">$title</a><br>\n";
642   }
643   print "<p><a href=\"nasmdoci.html\">Index</a>\n";
644   print "</body></html>\n";
645   select STDOUT;
646   close TEXT;
647
648   # Open a null file, to ensure output (eg random &html_jumppoints calls)
649   # goes _somewhere_.
650   print "writing chapter files...";
651   open TEXT,">/dev/null";
652   select TEXT;
653   $html_lastf = '';
654
655   $in_list = 0;
656
657   for ($para = 0; $para <= $#pnames; $para++) {
658     $pname = $pnames[$para];
659     $pflags = $pflags[$para];
660     $ptype = substr($pflags,0,4);
661
662     $in_list = 0, print "</ul>\n" if $in_list && $ptype ne "bull";
663     if ($ptype eq "chap") {
664       # Chapter heading. Begin a new file.
665       $pflags =~ /chap (.*) :(.*)/;
666       $title = "Chapter $1: ";
667       $xref = $2;
668       &html_jumppoints; print "</body></html>\n"; select STDOUT; close TEXT;
669       $html_lastf = $html_fnames{$chapternode};
670       $chapternode = $nodexrefs{$xref};
671       $html_nextf = $html_fnames{$tstruct_mnext{$chapternode}};
672       open TEXT,">$html_fnames{$chapternode}"; select TEXT; &html_preamble(1);
673       foreach $i (@$pname) {
674         $ww = &word_html($i);
675         $title .= $ww unless $ww eq "\001";
676       }
677       $h = "<h2><a name=\"$xref\">$title</a></h2>\n";
678       print $h; print FULL $h;
679     } elsif ($ptype eq "appn") {
680       # Appendix heading. Begin a new file.
681       $pflags =~ /appn (.*) :(.*)/;
682       $title = "Appendix $1: ";
683       $xref = $2;
684       &html_jumppoints; print "</body></html>\n"; select STDOUT; close TEXT;
685       $html_lastf = $html_fnames{$chapternode};
686       $chapternode = $nodexrefs{$xref};
687       $html_nextf = $html_fnames{$tstruct_mnext{$chapternode}};
688       open TEXT,">$html_fnames{$chapternode}"; select TEXT; &html_preamble(1);
689       foreach $i (@$pname) {
690         $ww = &word_html($i);
691         $title .= $ww unless $ww eq "\001";
692       }
693       print "<h2><a name=\"$xref\">$title</a></h2>\n";
694     } elsif ($ptype eq "head" || $ptype eq "subh") {
695       # Heading or subheading.
696       $pflags =~ /.... (.*) :(.*)/;
697       $hdr = ($ptype eq "subh" ? "h4" : "h3");
698       $title = $1 . " ";
699       $xref = $2;
700       foreach $i (@$pname) {
701         $ww = &word_html($i);
702         $title .= $ww unless $ww eq "\001";
703       }
704       print "<$hdr><a name=\"$xref\">$title</a></$hdr>\n";
705     } elsif ($ptype eq "code") {
706       # Code paragraph.
707       print "<p><pre>\n";
708       foreach $i (@$pname) {
709         $w = $i;
710         $w =~ s/&/&amp;/g;
711         $w =~ s/</&lt;/g;
712         $w =~ s/>/&gt;/g;
713         print $w, "\n";
714       }
715       print "</pre>\n";
716     } elsif ($ptype eq "bull" || $ptype eq "norm") {
717       # Ordinary paragraph, optionally bulleted. We wrap, with ragged
718       # 75-char right margin and either 7 or 11 char left margin
719       # depending on bullets.
720       if ($ptype eq "bull") {
721         $in_list = 1, print "<ul>\n" unless $in_list;
722         $line = '<li>';
723       } else {
724         $line = '<p>';
725       }
726       @a = @$pname;
727       $wd = $wprev = '';
728       do {
729         do { $w = &word_html(shift @a) } while $w eq "\001"; # nasty hack
730         $wd .= $wprev;
731         if ($w eq ' ' || $w eq '' || $w eq undef) {
732           if (length ($line . $wd) > 75) {
733             $line =~ s/\s*$//; # trim trailing spaces
734             print "$line\n";
735             $line = '';
736             $wd =~ s/^\s*//; # trim leading spaces
737           }
738           $line .= $wd;
739           $wd = '';
740         }
741         $wprev = $w;
742       } while ($w ne '' && $w ne undef);
743       if ($line =~ /\S/) {
744         $line =~ s/\s*$//; # trim trailing spaces
745         print "$line\n";
746       }
747     }
748   }
749
750   # Close whichever file was open.
751   &html_jumppoints;
752   print "</body></html>\n";
753   select STDOUT;
754   close TEXT;
755
756   print "\n   writing index file...";
757   open TEXT,">nasmdoci.html";
758   select TEXT;
759   &html_preamble(0);
760   print "<p align=center><a href=\"nasmdoc0.html\">Contents</a>\n";
761   print "<p>";
762   &html_index;
763   print "<p align=center><a href=\"nasmdoc0.html\">Contents</a>\n";
764   print "</body></html>\n";
765   select STDOUT;
766   close TEXT;
767 }
768
769 sub html_preamble {
770   print "<html><head><title>NASM Manual</title></head>\n";
771   print "<body><h1 align=center>The Netwide Assembler: NASM</h1>\n\n";
772   &html_jumppoints if $_[0];
773 }
774
775 sub html_jumppoints {
776   print "<p align=center>";
777   print "<a href=\"$html_nextf\">Next Chapter</a> |\n" if $html_nextf;
778   print "<a href=\"$html_lastf\">Previous Chapter</a> |\n" if $html_lastf;
779   print "<a href=\"nasmdoc0.html\">Contents</a> |\n";
780   print "<a href=\"nasmdoci.html\">Index</a>\n";
781 }
782
783 sub html_index {
784   my $itag, $a, @ientry, $sep, $w, $wd, $wprev, $line;
785
786   $chapternode = '';
787   foreach $itag (@itags) {
788     $ientry = $idxmap{$itag};
789     @a = @$ientry;
790     push @a, "n :";
791     $sep = 0;
792     foreach $node (@nodes) {
793       next if !$idxnodes{$node,$itag};
794       push @a, "n ," if $sep;
795       push @a, "sp", "x $xrefnodes{$node}", "n $node", "xe$xrefnodes{$node}";
796       $sep = 1;
797     }
798     $line = '';
799     do {
800       do { $w = &word_html(shift @a) } while $w eq "\001"; # nasty hack
801       $wd .= $wprev;
802       if ($w eq ' ' || $w eq '' || $w eq undef) {
803         if (length ($line . $wd) > 75) {
804           $line =~ s/\s*$//; # trim trailing spaces
805           print "$line\n";
806           $line = '';
807           $wd =~ s/^\s*//; # trim leading spaces
808         }
809         $line .= $wd;
810         $wd = '';
811       }
812       $wprev = $w;
813     } while ($w ne '' && $w ne undef);
814     if ($line =~ /\S/) {
815       $line =~ s/\s*$//; # trim trailing spaces
816       print "$line\n";
817     }
818     print "<br>\n";
819   }
820 }
821
822 sub word_html {
823   my ($w) = @_;
824   my $wtype, $wmajt, $pfx, $sfx;
825
826   return undef if $w eq '' || $w eq undef;
827
828   $wtype = substr($w,0,2);
829   $wmajt = substr($wtype,0,1);
830   $w = substr($w,2);
831   $pfx = $sfx = '';
832   $pfx = "<a href=\"$1\">", $sfx = "</a>", $w = $2
833     if $wmajt eq "w" && $w =~ /^<(.*)>(.*)$/;
834   $w =~ s/&/&amp;/g;
835   $w =~ s/</&lt;/g;
836   $w =~ s/>/&gt;/g;
837   if ($wmajt eq "n" || $wtype eq "e " || $wtype eq "w ") {
838     return $pfx . $w . $sfx;
839   } elsif ($wtype eq "sp") {
840     return ' ';
841   } elsif ($wtype eq "da") {
842     return '-'; # sadly, en-dashes are non-standard in HTML
843   } elsif ($wmajt eq "c" || $wtype eq "wc") {
844     return $pfx . "<code><nobr>${w}</nobr></code>" . $sfx;
845   } elsif ($wtype eq "es") {
846     return "<em>${w}";
847   } elsif ($wtype eq "ee") {
848     return "${w}</em>";
849   } elsif ($wtype eq "eo") {
850     return "<em>${w}</em>";
851   } elsif ($wtype eq "x ") {
852     # Magic: we must resolve the cross reference into file and marker
853     # parts, then dispose of the file part if it's us, and dispose of
854     # the marker part if the cross reference describes the top node of
855     # another file.
856     my $node = $nodexrefs{$w}; # find the node we're aiming at
857     my $level = $tstruct_level{$node}; # and its level
858     my $up = $node, $uplev = $level-1;
859     $up = $tstruct_up{$up} while $uplev--; # get top node of containing file
860     my $file = ($up ne $chapternode) ? $html_fnames{$up} : "";
861     my $marker = ($level == 1 and $file) ? "" : "#$w";
862     return "<a href=\"$file$marker\">";
863   } elsif ($wtype eq "xe") {
864     return "</a>";
865   } elsif ($wmajt eq "i") {
866     return "\001";
867   } else {
868     die "panic in word_html: $wtype$w\n";
869   }
870 }
871
872 sub ref_ps {
873     my($r) = @_;
874     $r =~ s/\./_/g;
875     return 'n'.$r;
876 }
877
878 sub ps_write_bookmarks {
879     my $para;
880     my %nchildren = ();
881     my %titles = ();
882     my @reflist = ();
883     my $ref, $pref, $i, $title;
884
885     for ($para = 0; $para <= $#pnames; $para++) {
886         my $pname = $pnames[$para];
887         my $pflags = $pflags[$para];
888         my $ptype = substr($pflags,0,4);
889         
890         if ($ptype eq "chap" || $ptype eq "appn") {
891             # Chapter/appendix heading. "Chapter N: Title" followed by a line of
892             # minus signs.
893             
894             $pflags =~ /(chap|appn) (.*) :(.*)/;
895             $ref = &ref_ps($2);
896             $title = '';
897             foreach $i (@$pname) {
898                 $title .= &word_ps_title($i);
899             }
900             $titles{$ref} = $title;
901             push @reflist, $ref;
902         } elsif ($ptype eq "head" || $ptype eq "subh") {
903             # Heading/subheading.  Just a number and some text.
904             $pflags =~ /.... (.*) :(.*)/;
905             $ref = &ref_ps($1);
906             $ref =~ /^(n[0-9A-Za-z_]+)\_[0-9A-Za-z]+$/;
907             $pref = $1;
908
909             $title = '';
910             foreach $i (@$pname) {
911                 $title .= &word_ps_title($i);
912             }
913             $titles{$ref} = $title;
914             push @reflist, $ref;
915             $nchildren{$pref}++;
916         }
917     }
918
919     # Now we should have enough data to generate the bookmarks
920     print "[/Title (Contents) /Dest /nContents /OUT pdfmark";
921     foreach $i ( @reflist ) {
922         print '[/Title (', $titles{$i}, ")\n";
923         print '/Count -', $nchildren{$i}, ' ' if ( $nchildren{$i} );
924         print "/Dest /$i /OUT pdfmark\n";
925     }
926     print "[/Title (Index) /Dest /nIndex /OUT pdfmark\n";
927 }
928
929 sub write_ps {
930   # This is called from the top level, so I won't bother using
931   # my or local.
932
933   # First, set up the font metric arrays.
934   &font_metrics;
935
936   # First stage: reprocess the source arrays into a list of
937   # lines, each of which is a list of word-strings, each of
938   # which has a single-letter font code followed by text.
939   # Each line also has an associated type, which will be
940   # used for final alignment and font selection and things.
941   #
942   # Font codes are:
943   #   n == Normal
944   #   e == Emphasised
945   #   c == Code
946   #  ' ' == space (no following text required)
947   #  '-' == dash (no following text required)
948   #
949   # Line types are:
950   #   chap == Chapter or appendix heading.
951   #   head == Major heading.
952   #   subh == Sub-heading.
953   #   Ccha == Contents entry for a chapter.
954   #   Chea == Contents entry for a heading.
955   #   Csub == Contents entry for a subheading.
956   #   cone == Code paragraph with just this one line on it.
957   #   cbeg == First line of multi-line code paragraph.
958   #   cbdy == Interior line of multi-line code paragraph.
959   #   cend == Final line of multi-line code paragraph.
960   #   none == Normal paragraph with just this one line on it.
961   #   nbeg == First line of multi-line normal paragraph.
962   #   nbdy == Interior line of multi-line normal paragraph.
963   #   nend == Final line of multi-line normal paragraph.
964   #   bone == Bulleted paragraph with just this one line on it.
965   #   bbeg == First line of multi-line bulleted paragraph.
966   #   bbdy == Interior line of multi-line bulleted paragraph.
967   #   bend == Final line of multi-line bulleted paragraph.
968   print "line-breaks...";
969   $lname = "psline000000";
970   $lnamei = "idx" . $lname;
971   @lnames = @ltypes = ();
972
973   $linewidth = 468;             # ADJUSTABLE: width of a normal text line
974   $bulletadj = 12;              # ADJUSTABLE: space for a bullet
975
976   for ($para = 0; $para <= $#pnames; $para++) {
977     $pname = $pnames[$para];
978     $pflags = $pflags[$para];
979     $ptype = substr($pflags,0,4);
980
981     # New paragraph _ergo_ new line.
982     @line = ();
983     @lindex = (); # list of index tags referenced to this line
984
985     if ($ptype eq "chap") {
986       # Chapter heading. "Chapter N: Title" followed by a line of
987       # minus signs.
988       $pflags =~ /chap (.*) :(.*)/;
989       push @line, "B".&ref_ps($1), "nChapter", " ", "n$1:", " ";
990       foreach $i (@$pname) {
991         $ww = &word_ps($i);
992         push @line, $ww unless $ww eq "x";
993       }
994       @$lname = @line; @$lnamei = @lindex;
995       push @lnames, $lname++;
996       $lnamei = "idx" . $lname;
997       push @ltypes, "chap";
998     } elsif ($ptype eq "appn") {
999       # Appendix heading. "Appendix N: Title" followed by a line of
1000       # minus signs.
1001       $pflags =~ /appn (.*) :(.*)/;
1002       push @line, "B".&ref_ps($1), "nAppendix", " ", "n$1:", " ";
1003       foreach $i (@$pname) {
1004         $ww = &word_ps($i);
1005         push @line, $ww unless $ww eq "x";
1006       }
1007       @$lname = @line; @$lnamei = @lindex;
1008       push @lnames, $lname++;
1009       $lnamei = "idx" . $lname;
1010       push @ltypes, "chap";
1011     } elsif ($ptype eq "head") {
1012       # Heading. Just a number and some text.
1013       $pflags =~ /.... (.*) :(.*)/;
1014       push @line, "B".&ref_ps($1), "n$1";
1015       foreach $i (@$pname) {
1016         $ww = &word_ps($i);
1017         push @line, $ww unless $ww eq "x";
1018       }
1019       @$lname = @line; @$lnamei = @lindex;
1020       push @lnames, $lname++;
1021       $lnamei = "idx" . $lname;
1022       push @ltypes, $ptype;
1023     } elsif ($ptype eq "subh") {
1024       # Subheading. Just a number and some text.
1025       $pflags =~ /subh (.*) :(.*)/;
1026       push @line, "B".&ref_ps($1), "n$1";
1027       foreach $i (@$pname) {
1028         push @line, &word_ps($i);
1029       }
1030       @$lname = @line; @$lnamei = @lindex;
1031       push @lnames, $lname++;
1032       $lnamei = "idx" . $lname;
1033       push @ltypes, "subh";
1034     } elsif ($ptype eq "code") {
1035       # Code paragraph. Emit lines one at a time.
1036       $type = "cbeg";
1037       foreach $i (@$pname) {
1038         @$lname = ("c$i");
1039         push @lnames, $lname++;
1040         $lnamei = "idx" . $lname;
1041         push @ltypes, $type;
1042         $type = "cbdy";
1043       }
1044       $ltypes[$#ltypes] = ($ltypes[$#ltypes] eq "cbeg" ? "cone" : "cend");
1045     } elsif ($ptype eq "bull" || $ptype eq "norm") {
1046       # Ordinary paragraph, optionally bulleted. We wrap, with ragged
1047       # 75-char right margin and either 7 or 11 char left margin
1048       # depending on bullets.
1049       if ($ptype eq "bull") {
1050         $width = $linewidth - $bulletadj;
1051         $type = $begtype = "bbeg";
1052         $bodytype = "bbdy";
1053         $onetype = "bone";
1054         $endtype = "bend";
1055       } else {
1056         $width = $linewidth;
1057         $type = $begtype = "nbeg";
1058         $bodytype = "nbdy";
1059         $onetype = "none";
1060         $endtype = "nend";
1061       }
1062       @a = @$pname;
1063       @line = @wd = ();
1064       $linelen = 0;
1065       $wprev = undef;
1066       do {
1067         do { $w = &word_ps(shift @a) } while ($w eq "x");
1068         push @wd, $wprev if $wprev;
1069         if ($wprev =~ /^n.*-$/ || $w eq ' ' || $w eq '' || $w eq undef) {
1070           $wdlen = &len_ps(@wd);
1071           if ($linelen + $wdlen > $width) {
1072             pop @line while $line[$#line] eq ' '; # trim trailing spaces
1073             @$lname = @line; @$lnamei = @lindex;
1074             push @lnames, $lname++;
1075             $lnamei = "idx" . $lname;
1076             push @ltypes, $type;
1077             $type = $bodytype;
1078             @line = @lindex = ();
1079             $linelen = 0;
1080             shift @wd while $wd[0] eq ' '; # trim leading spaces
1081           }
1082           push @line, @wd;
1083           $linelen += $wdlen;
1084           @wd = ();
1085         }
1086         $wprev = $w;
1087       } while ($w ne '' && $w ne undef);
1088       if (@line) {
1089         pop @line while $line[$#line] eq ' '; # trim trailing spaces
1090         @$lname = @line; @$lnamei = @lindex;
1091         push @lnames, $lname++;
1092         $lnamei = "idx" . $lname;
1093         push @ltypes, $type;
1094         $type = $bodytype;
1095       }
1096       $ltypes[$#ltypes] =
1097         ($ltypes[$#ltypes] eq $begtype ? $onetype : $endtype);
1098     }
1099   }
1100
1101   # We've now processed the document source into lines. Before we
1102   # go on and do the page breaking, we'll fabricate a table of contents,
1103   # line by line, and then after doing page breaks we'll go back and
1104   # insert the page numbers into the contents entries.
1105   print "building contents...";
1106   @clnames = @cltypes = ();
1107   $clname = "pscont000000";
1108   @$clname = ("BnContents", "nContents"); # "chapter heading" for TOC
1109   push @clnames,$clname++;
1110   push @cltypes,"chap";
1111   for ($i=0; $i<=$#lnames; $i++) {
1112     $lname = $lnames[$i];
1113     if ($ltypes[$i] =~ /^(chap|head|subh)/) {
1114       @$clname = @$lname;
1115       splice @$clname,2,0," " if ($ltypes[$i] !~ /chap/);
1116       push @$clname,$i; # placeholder for page number
1117       push @clnames,$clname++;
1118       push @cltypes,"C" . substr($ltypes[$i],0,3);
1119     }
1120   }
1121   @$clname = ("BnIndex", "nIndex"); # contents entry for Index
1122   push @$clname,$i;      # placeholder for page number
1123   $idx_clname = $clname;
1124   push @clnames,$clname++;
1125   push @cltypes,"Ccha";
1126   $contlen = $#clnames + 1;
1127   unshift @lnames,@clnames;
1128   unshift @ltypes,@cltypes;
1129
1130   # Second stage: now we have a list of lines, break them into pages.
1131   # We do this by means of adding a third array in parallel with
1132   # @lnames and @ltypes, called @lpages, in which we store the page
1133   # number that each line resides on. We also add @ycoord which
1134   # stores the vertical position of each line on the page.
1135   #
1136   # Page breaks may not come after line-types:
1137   #   chap head subh cbeg nbeg bbeg
1138   # and may not come before line-types:
1139   #   cend nend bend
1140   # They are forced before line-types:
1141   #   chap
1142   print "page-breaks...";
1143   $pmax = 600; # ADJUSTABLE: maximum length of a page in points
1144   $textht = 11; # ADJUSTABLE: height of a normal line in points
1145   $spacing = 6; # ADJUSTABLE: space between paragraphs, in points
1146   $headht = 14; # ADJUSTABLE: height of a major heading in points
1147   $subht = 12; # ADJUSTABLE: height of a sub-heading in points
1148   $pstart = 0; # start line of current page
1149   $plen = 0; # current length of current page
1150   $pnum = 1; # number of current page
1151   $bpt = -1; # last feasible break point
1152   $i = 0; # line number
1153   while ($i <= $#lnames) {
1154     $lname = $lnames[$i];
1155     # Add the height of this line (computed the last time we went round
1156     # the loop, unless we're a chapter heading in which case we do it
1157     # now) to the length of the current page. Also, _put_ this line on
1158     # the current page, and allocate it a y-coordinate.
1159     if ($ltypes[$i] =~ /^chap$/) {
1160       $pnum += 1 - ($pnum & 1);  # advance to odd numbered page if necessary
1161       $plen = 100; # ADJUSTABLE: space taken up by a chapter heading
1162       $ycoord[$i] = 0; # chapter heading: y-coord doesn't matter
1163     } else {
1164       $ycoord[$i] = $plen + $space;
1165       $plen += $space + $ht;
1166     }
1167     # See if we can break after this line.
1168     $bpt = $i if $ltypes[$i] !~ /^chap|head|subh|cbeg|nbeg|bbeg$/ &&
1169                  $ltypes[$i+1] !~ /^cend|nend|bend$/;
1170     # Assume, to start with, that we don't break after this line.
1171     $break = 0;
1172     # See if a break is forced.
1173     $break = 1, $bpt = $i if $ltypes[$i+1] eq "chap" || !$ltypes[$i+1];
1174     # Otherwise, compute the height of the next line, and break if
1175     # it would make this page too long.
1176     $ht = $textht, $space = 0 if $ltypes[$i+1] =~ /^[nbc](bdy|end)$/;
1177     $ht = $textht, $space = $spacing if $ltypes[$i+1] =~ /^[nbc](one|beg)$/;
1178     $ht = $textht, $space = $spacing if $ltypes[$i+1] =~ /^C/;
1179     $ht = $subht, $space = $spacing if $ltypes[$i+1] eq "subh";
1180     $ht = $headht, $space = $spacing if $ltypes[$i+1] eq "head";
1181     $break = 1 if $plen + $space + $ht > $pmax;
1182     # Now, if we're breaking, assign page number $pnum to all lines up
1183     # to $bpt, set $i == $bpt+1, and zero $space since we are at the
1184     # start of a new page and don't want leading space.
1185     if ($break) {
1186       die "no feasible break point at all on page $pnum\n" if $bpt == -1;
1187       for ($j = $pstart; $j <= $bpt; $j++) {
1188         $lnamei = "idx" . $lnames[$j];
1189         foreach $k (@$lnamei) {
1190           ${$psidxpp{$k}}{$pnum} = 1;
1191         }
1192         $lpages[$j] = $pnum;
1193       }
1194       $pnum++;
1195       $i = $bpt;
1196       $bpt = -1;
1197       $pstart = $i+1;
1198       $plen = 0;
1199       $space = 0;
1200     }
1201     $i++;
1202   }
1203
1204   # Now fix up the TOC with page numbers.
1205   print "\n   fixing up contents...";
1206   for ($i=0; $i<=$#lnames; $i++) {
1207     $lname = $lnames[$i];
1208     if ($ltypes[$i] =~ /^C/) {
1209       $j = pop @$lname;
1210       push @$lname, "n" . $lpages[$j+$contlen];
1211     }
1212   }
1213
1214   # Having got page numbers for most stuff, generate an index.
1215   print "building index...";
1216   $iwid = 222;
1217   $sep = 12;
1218   $commaindent = 32;
1219   foreach $k (@itags) {
1220     @line = ();
1221     $cmd = "index";
1222     @idxentry = @{$idxmap{$k}};
1223     if ($commaafter{$k} and !$commanext{$k}) {
1224       # This line is a null line beginning a multiple entry. We must
1225       # output the prefix on a line by itself.
1226
1227       @idxhead = splice @idxentry,0,$commapos{$k};
1228       @line = ();
1229       foreach $i (@idxhead) {
1230         $ww = &word_ps($i);
1231         push @line, $ww unless $ww eq "x";
1232       }
1233       &ps_idxout("index",\@line,[]);
1234       $cmd = "iindex";
1235       @line = ();
1236     }
1237     $cmd = "iindex", splice @idxentry,0,$commapos{$k} if $commanext{$k};
1238     foreach $i (@idxentry) {
1239       $ww = &word_ps($i);
1240       push @line, $ww unless $ww eq "x";
1241     }
1242     $len = $iwid - $sep - &len_ps(@line);
1243     warn "text for index tag `%s' is longer than one index line!\n"
1244       if $len < -$sep;
1245     @pp = ();
1246     $inums = join(',',sort { $a <=> $b } keys %{$psidxpp{$k}});
1247     while (length $inums) {
1248       $inums =~ /^([^,]+)(,?)(.*)$/;
1249       $inums = $3, $inumc = $2; $inum = $1;
1250       @pnum = (" ", "Bp$inum", "n$inum", "E");
1251       push(@pnum, "n$inumc") if ( $inumc ne '' );
1252       $pnumlen = &len_ps(@pnum);
1253       if ($pnumlen > $len) {
1254         &ps_idxout($cmd,\@line,\@pp);
1255         @pp = ();
1256         @line = ();
1257         $cmd = "index";
1258         $len = $iwid - $sep;
1259       }
1260       push @pp, @pnum;
1261       $len -= $pnumlen;
1262     }
1263     &ps_idxout($cmd,\@line,\@pp) if (length @pp);
1264     $l1 = &len_ps(@line);
1265     $l2 = &len_ps($pp);
1266   }
1267   $$idx_clname[$#$idx_clname] = "n" . $pnum; # fix up TOC entry for index
1268
1269   print "writing file...";
1270   open PS,">nasmdoc.ps";
1271   select PS;
1272   $page = $lpages[0];
1273   &ps_header;
1274   &ps_write_bookmarks;
1275   for ($i=0; $i<=$#lnames; $i++) {
1276     &ps_throw_pg($page,$lpages[$i]) if $page != $lpages[$i];
1277     $page = $lpages[$i];
1278     &ps_out_line($ycoord[$i],$ltypes[$i],$lnames[$i]);
1279   }
1280   $i = 0;
1281   while ($i <= $#psindex) {
1282     &ps_throw_pg($page, $pnum) if $page != $pnum;
1283     $page = $pnum++;
1284     $ypos = 0;
1285     $ypos = 100, &ps_out_line(0, "chap", ["BnIndex", "nIndex"]) if !$i;
1286     $lines = ($pmax - $ypos) / $textht;
1287     my $col; # ps_out_line hits this variable
1288     PAGE:for ($col = 1; $col <= 2; $col++) {
1289       $y = $ypos; $l = $lines;
1290       COL: while ($l > 0) {
1291         $j = $i+1;
1292         $j++ while $psindex[$j] and ($psindex[$j][3] == 0); # find next break
1293         last COL if $j-$i > $l or $i > $#psindex;
1294         while ($i < $j) {
1295           &ps_out_line($y, $psindex[$i][0] eq "index" ? "idl$col" : "ldl$col",
1296                        $psindex[$i][1]);
1297           &ps_out_line($y,"idr$col",$psindex[$i][2]);
1298           $i++;
1299           $y += $textht;
1300           $l--;
1301         }
1302       }
1303       last PAGE if $i > $#psindex;
1304     }
1305   }
1306   &ps_trailer($page);
1307   close PS;
1308   select STDOUT;
1309 }
1310
1311 sub ps_idxout {
1312   my ($cmd, $left, $right) = @_;
1313   my $break = 1;
1314   $break = 0
1315       if ($#psindex >= 0) and ( ($#$left < 0) or ($cmd eq "iindex") );
1316   push @psindex,[$cmd,[@$left],[@$right],$break];
1317 }
1318
1319 sub ps_header {
1320     $pshdr = <<'EOF';
1321 /sp (n ) def
1322 /nf /Times-Roman findfont 11 scalefont def
1323 /ef /Times-Italic findfont 11 scalefont def
1324 /cf /Courier findfont 11 scalefont def
1325 /nc /Helvetica-Bold findfont 18 scalefont def
1326 /ec /Helvetica-Oblique findfont 18 scalefont def
1327 /cc /Courier-Bold findfont 18 scalefont def
1328 /nh /Helvetica-Bold findfont 14 scalefont def
1329 /eh /Helvetica-Oblique findfont 14 scalefont def
1330 /ch /Courier-Bold findfont 14 scalefont def
1331 /ns /Helvetica-Bold findfont 12 scalefont def
1332 /es /Helvetica-Oblique findfont 12 scalefont def
1333 /cs /Courier-Bold findfont 12 scalefont def
1334 /n 16#6E def /e 16#65 def /c 16#63 def
1335 /B 16#42 def /E 16#45 def /D 16#44 def
1336 /min { 2 copy gt { exch } if pop } def
1337 /max { 2 copy lt { exch } if pop } def
1338 /lkbegun 0 def
1339 /lkury 0 def
1340 /lkurx 0 def
1341 /lklly 0 def
1342 /lkllx 0 def
1343 /lktarget () def
1344 /linkbegin {
1345   /lkbegun 1 def
1346   /lktarget exch cvn def
1347 } def
1348 /linkshow {
1349   lkbegun 0 ne {
1350     gsave dup true charpath pathbbox grestore
1351     lkbegun 1 eq {
1352       /lkury exch def
1353       /lkurx exch def
1354       /lklly exch def
1355       /lkllx exch def
1356       /lkbegun 2 def
1357     } {
1358       lkury max /lkury exch def
1359       lkurx max /lkurx exch def
1360       lklly min /lklly exch def
1361       lkllx min /lkllx exch def
1362     } ifelse
1363   } if
1364   show
1365 } def
1366 /linkend {
1367   [/Rect [ lkllx lklly lkurx lkury ]
1368     /Color [ 1.0 0.0 0.0 ]
1369     /Border [0 0 0]
1370     /Dest lktarget
1371     /Subtype /Link
1372     /ANN pdfmark
1373   /lkbegun 0 def
1374 } def
1375 /linkdest {
1376   /lkdest exch cvn def
1377   [ /Dest lkdest
1378     /View [ /XYZ currentpoint 0 ]
1379     /DEST pdfmark
1380 } def
1381 /handlelink {
1382   dup 0 get
1383   dup B eq {
1384     pop dup length 1 sub 1 exch getinterval linkbegin
1385   } {
1386     E eq {
1387       pop linkend
1388     } {
1389       dup length 1 sub 1 exch getinterval linkdest
1390     } ifelse
1391   } ifelse
1392 } def
1393 /pageodd {
1394    550 50 moveto ns setfont dup stringwidth pop neg 0 rmoveto show
1395 } def
1396 /pageeven { 50 50 moveto ns setfont show } def
1397 /destmark {
1398   dup length 1 sub 1 exch getinterval linkdest
1399 } def
1400 /chapter {
1401   100 620 moveto
1402   dup 0 get destmark
1403   dup length 1 sub 1 exch getinterval
1404   {
1405     dup 0 get
1406     dup n eq {pop nc setfont} {
1407       e eq {ec setfont} {cc setfont} ifelse
1408     } ifelse
1409     dup length 1 sub 1 exch getinterval show
1410   } forall
1411   0 setlinecap 3 setlinewidth
1412   newpath 100 610 moveto 468 0 rlineto stroke
1413 } def
1414 /heading {
1415   686 exch sub /y exch def /a exch def
1416   90 y moveto
1417   a 0 get destmark
1418   a 1 get dup length 1 sub 1 exch getinterval
1419   nh setfont dup stringwidth pop neg 0 rmoveto show
1420   100 y moveto
1421   a dup length 2 sub 2 exch getinterval {
1422     /s exch def
1423     s 0 get
1424     dup n eq {pop nh setfont} {
1425       e eq {eh setfont} {ch setfont} ifelse
1426     } ifelse
1427     s s length 1 sub 1 exch getinterval show
1428   } forall
1429 } def
1430 /subhead {
1431   688 exch sub /y exch def /a exch def
1432   90 y moveto
1433   a 0 get destmark
1434   a 1 get dup length 1 sub 1 exch getinterval
1435   ns setfont dup stringwidth pop neg 0 rmoveto show
1436   100 y moveto
1437   a dup length 2 sub 2 exch getinterval {
1438     /s exch def
1439     s 0 get
1440     dup n eq {pop ns setfont} {
1441       e eq {es setfont} {cs setfont} ifelse
1442     } ifelse
1443     s s length 1 sub 1 exch getinterval show
1444   } forall
1445 } def
1446 /disp { /j exch def
1447   568 exch sub exch 689 exch sub moveto
1448   {
1449     /s exch def
1450     s 0 get
1451     dup E le {
1452       pop s handlelink
1453     } {
1454       dup n eq {pop nf setfont} {
1455         e eq {ef setfont} {cf setfont} ifelse
1456       } ifelse
1457       s s length 1 sub 1 exch getinterval linkshow
1458       s sp eq {j 0 rmoveto} if
1459     } ifelse
1460   } forall
1461 } def
1462 /contents { /w exch def /y exch def /a exch def
1463   /yy 689 y sub def
1464   a a length 1 sub get dup length 1 sub 1 exch getinterval
1465   /ss exch def
1466   nf setfont 568 ss stringwidth pop sub /ex exch def
1467   a 0 a length 1 sub getinterval y w 0 disp
1468   /sx currentpoint pop def nf setfont
1469   100 10 568 { /i exch def
1470     i 5 sub sx gt i 5 add ex lt and {
1471       i yy moveto (.) linkshow
1472     } if
1473   } for
1474   ex yy moveto ss linkshow
1475   linkend
1476 } def
1477 /just { /w exch def /y exch def /a exch def
1478   /jj w def /spaces 0 def
1479   a {
1480     /s exch def
1481     s 0 get
1482     dup n eq {pop nf setfont} {
1483       e eq {ef setfont} {cf setfont} ifelse
1484     } ifelse
1485     s s length 1 sub 1 exch getinterval stringwidth pop
1486     jj exch sub /jj exch def
1487     s sp eq {/spaces spaces 1 add def} if
1488   } forall
1489   a y w jj spaces spaces 0 eq {pop pop 0} {div} ifelse disp
1490 } def
1491 /idl { 468 exch sub 0 disp } def
1492 /ldl { 436 exch sub 0 disp } def
1493 /idr { 222 add 468 exch sub /x exch def /y exch def /a exch def
1494   a {
1495     /s exch def
1496     s 0 get
1497     dup E le {
1498       pop
1499     } {
1500       dup n eq {pop nf setfont} {
1501         e eq {ef setfont} {cf setfont} ifelse
1502       } ifelse
1503       s s length 1 sub 1 exch getinterval stringwidth pop
1504       x add /x exch def
1505     } ifelse
1506   } forall
1507   a y x 0 disp
1508 } def
1509 /left {0 disp} def
1510 /bullet {
1511   nf setfont dup 100 exch 689 exch sub moveto (\267) show
1512 } def
1513 [/PageMode /UseOutlines /DOCVIEW pdfmark
1514 EOF
1515   print "%!PS-Adobe-3.0\n";
1516   print "%%BoundingBox: 95 95 590 705\n";
1517   print "%%Creator: a nasty Perl script\n";
1518   print "%%DocumentData: Clean7Bit\n";
1519   print "%%Orientation: Portrait\n";
1520   print "%%Pages: $lpages[$#lpages]\n";
1521   print "%%DocumentNeededResources: font Times-Roman Times-Italic\n";
1522   print "%%+ font Helvetica-Bold Courier Courier-Bold\n";
1523   print "%%EndComments\n";
1524   print "%%BeginProlog\n";
1525   # This makes sure non-PDF PostScript interpreters don't choke on
1526   # pdfmarks in the output
1527   print "/pdfmark where\n";
1528   print "{pop} {userdict /pdfmark /cleartomark load put} ifelse\n";
1529   print "%%EndProlog\n";
1530   print "%%BeginSetup\n";
1531   print "save\n";
1532   $pshdr =~ s/\s+/ /g;
1533   while ($pshdr =~ /\S/) {
1534     last if length($pshdr) < 72 || $pshdr !~ /^(.{0,72}\S)\s(.*)$/;
1535     $pshdr = $2;
1536     print "$1\n";
1537   }
1538   print "$pshdr\n" if $pshdr =~ /\S/;
1539   print "%%EndSetup\n";
1540   &ps_initpg($lpages[0]);
1541 }
1542
1543 sub ps_trailer {
1544   my ($oldpg) = @_;
1545   &ps_donepg($oldpg);
1546   print "%%Trailer\nrestore\n%%EOF\n";
1547 }
1548
1549 sub ps_throw_pg {
1550   my ($oldpg, $newpg) = @_;
1551   while ($oldpg < $newpg) {
1552     &ps_donepg($oldpg);
1553     $oldpg++;
1554     &ps_initpg($oldpg);
1555   }
1556 }
1557
1558 sub ps_initpg {
1559   my ($pgnum) = @_;
1560   print "%%Page: $pgnum $pgnum\n";
1561   print "%%BeginPageSetup\nsave\n%%EndPageSetup\n";
1562   print "95 705 moveto (p$pgnum) linkdest\n";
1563 }
1564
1565 sub ps_donepg {
1566   my ($pgnum) = @_;
1567   if ($pgnum & 1) {
1568     print "%%PageTrailer\n($pgnum)pageodd restore showpage\n";
1569   } else {
1570     print "%%PageTrailer\n($pgnum)pageeven restore showpage\n";
1571   }
1572 }
1573
1574 sub ps_out_line {
1575   my ($ypos,$ltype,$lname) = @_;
1576   my $c,$d,$wid;
1577
1578   print "[";
1579   $col = 1;
1580   foreach $c (@$lname) {#
1581     $c= "n " if $c eq " ";
1582     $c = "n\261" if $c eq "-";
1583     $d = '';
1584     while (length $c) {
1585       $d .= $1, $c = $2 while $c =~ /^([ -\'\*-\[\]-~]+)(.*)$/;
1586       while (1) {
1587         $d .= "\\$1", $c = $2, next if $c =~ /^([\\\(\)])(.*)$/;
1588         ($d .= sprintf "\\%3o",unpack("C",$1)), $c = $2, next
1589           if $c =~ /^([^ -~])(.*)$/;
1590         last;
1591       }
1592     }
1593     $d = "($d)";
1594     $col = 0, print "\n" if $col>0 && $col+length $d > 77;
1595     print $d;
1596     $col += length $d;
1597   }
1598   print "\n" if $col > 60;
1599   print "]";
1600   if ($ltype =~ /^[nb](beg|bdy)$/) {
1601     printf "%d %s%d just\n",
1602       $ypos, ($ltype eq "bbeg" ? "bullet " : ""),
1603       ($ltype =~ /^b/ ? 456 : 468);
1604   } elsif ($ltype =~ /^[nb](one|end)$/) {
1605     printf "%d %s%d left\n",
1606       $ypos, ($ltype eq "bone" ? "bullet " : ""),
1607       ($ltype =~ /^b/ ? 456 : 468);
1608   } elsif ($ltype =~ /^c(one|beg|bdy|end)$/) {
1609     printf "$ypos 468 left\n";
1610   } elsif ($ltype =~ /^C/) {
1611     $wid = 468;
1612     $wid = 456 if $ltype eq "Chea";
1613     $wid = 444 if $ltype eq "Csub";
1614     printf "$ypos $wid contents\n";
1615   } elsif ($ltype eq "chap") {
1616     printf "chapter\n";
1617   } elsif ($ltype eq "head") {
1618     printf "$ypos heading\n";
1619   } elsif ($ltype eq "subh") {
1620     printf "$ypos subhead\n";
1621   } elsif ($ltype =~ /([il]d[lr])([12])/) {
1622     $left = ($2 eq "2" ? 468-222 : 0);
1623     printf "$ypos $left $1\n";
1624   }
1625 }
1626
1627 sub word_ps {
1628   my ($w) = @_;
1629   my $wtype, $wmajt;
1630
1631   return undef if $w eq '' || $w eq undef;
1632
1633   $wtype = substr($w,0,2);
1634   $wmajt = substr($wtype,0,1);
1635   $w = substr($w,2);
1636   $w =~ s/<.*>// if $wmajt eq "w"; # remove web links
1637   if ($wmajt eq "n" || $wtype eq "w ") {
1638     return "n$w";
1639   } elsif ($wtype eq "sp") {
1640     return ' ';
1641   } elsif ($wtype eq "da") {
1642     return '-';
1643   } elsif ($wmajt eq "c" || $wtype eq "wc") {
1644     return "c$w";
1645   } elsif ($wmajt eq "e") {
1646     return "e$w";
1647   } elsif ($wmajt eq "x") {
1648     return "x";
1649   } elsif ($wtype eq "i ") {
1650     push @lindex, $w;
1651     return "x";
1652   } else {
1653     die "panic in word_ps: $wtype$w\n";
1654   }
1655 }
1656
1657 sub word_ps_title {
1658   my ($w) = @_;
1659   my $wtype, $wmajt;
1660
1661   return undef if $w eq '' || $w eq undef;
1662
1663   $wtype = substr($w,0,2);
1664   $wmajt = substr($wtype,0,1);
1665   $w = substr($w,2);
1666   $w =~ s/<.*>// if $wmajt eq "w"; # remove web links
1667   if ($wmajt eq "n" || $wtype eq "w ") {
1668     return $w;
1669   } elsif ($wtype eq "sp") {
1670     return ' ';
1671   } elsif ($wtype eq "da") {
1672     return '-';
1673   } elsif ($wmajt eq "c" || $wtype eq "wc") {
1674     return $w;
1675   } elsif ($wmajt eq "e") {
1676     return $w;
1677   } elsif ($wmajt eq "x") {
1678     return '';
1679   } elsif ($wtype eq "i ") {
1680     return '';
1681   } else {
1682     die "panic in word_ps_title: $wtype$w\n";
1683   }
1684 }
1685
1686 sub len_ps {
1687   my (@line) = @_;
1688   my $l = 0;
1689   my $w, $size;
1690
1691   $size = 11/1000; # used only for length calculations
1692   while ($w = shift @line) {
1693     $w = "n " if $w eq " ";
1694     $w = "n\261" if $w eq "-";
1695     $f = substr($w,0,1);
1696     if ( $f !~ /^[BDE]$/ ) {
1697         $f = "timesr" if $f eq "n";
1698         $f = "timesi" if $f eq "e";
1699         $f = "courr" if $f eq "c";
1700         foreach $c (unpack 'C*',substr($w,1)) {
1701             $l += $size * $$f[$c];
1702         }
1703     }
1704   }
1705   return $l;
1706 }
1707
1708 sub write_texi {
1709   # This is called from the top level, so I won't bother using
1710   # my or local.
1711
1712   # Open file.
1713   print "writing file...";
1714   open TEXT,">nasmdoc.texi";
1715   select TEXT;
1716
1717   # Preamble.
1718   print "\\input texinfo   \@c -*-texinfo-*-\n";
1719   print "\@c \%**start of header\n";
1720   print "\@setfilename ",$metadata{'infofile'},".info\n";
1721   print "\@dircategory ",$metadata{'category'},"\n";
1722   print "\@direntry\n";
1723   printf "* %-28s %s.\n",
1724   sprintf('%s: (%s).', $metadata{'infoname'}, $metadata{'infofile'}),
1725   $metadata{'infotitle'};
1726   print "\@end direntry\n";
1727   print "\@settitle ",$metadata{'title'},"\n";
1728   print "\@setchapternewpage odd\n";
1729   print "\@c \%**end of header\n";
1730   print "\n";
1731   print "\@ifinfo\n";
1732   print $metadata{'summary'}, "\n";
1733   print "\n";
1734   print "Copyright ",$metadata{'year'}," ",$metadata{'author'},"\n";
1735   print "\n";
1736   print $metadata{'license'}, "\n";
1737   print "\@end ifinfo\n";
1738   print "\n";
1739   print "\@titlepage\n";
1740   print "\@title ",$metadata{'title'},"\n";
1741   print "\@author ",$metadata{'author'},"\n";
1742   print "\n";
1743   print "\@page\n";
1744   print "\@vskip 0pt plus 1filll\n";
1745   print "Copyright \@copyright{} ",$metadata{'year'},' ',$metadata{'author'},"\n";
1746   print "\n";
1747   print $metadata{'license'}, "\n";
1748   print "\@end titlepage\n";
1749   print "\n";
1750   print "\@node Top, $tstruct_next{'Top'}, (dir), (dir)\n";
1751   print "\@top ",$metadata{'infotitle'},"\n";
1752   print "\n";
1753   print "\@ifinfo\n";
1754   print $metadata{'summary'}, "\n";
1755   print "\@end ifinfo\n";
1756
1757   $node = "Top";
1758
1759   $bulleting = 0;
1760   for ($para = 0; $para <= $#pnames; $para++) {
1761     $pname = $pnames[$para];
1762     $pflags = $pflags[$para];
1763     $ptype = substr($pflags,0,4);
1764
1765     $bulleting = 0, print "\@end itemize\n" if $bulleting && $ptype ne "bull";
1766     print "\n"; # always one of these before a new paragraph
1767
1768     if ($ptype eq "chap") {
1769       # Chapter heading. Begin a new node.
1770       &texi_menu($node)
1771         if $tstruct_level{$tstruct_next{$node}} > $tstruct_level{$node};
1772       $pflags =~ /chap (.*) :(.*)/;
1773       $node = "Chapter $1";
1774       $title = "Chapter $1: ";
1775       foreach $i (@$pname) {
1776         $ww = &word_texi($i);
1777         $title .= $ww unless $ww eq "\001";
1778       }
1779       print "\@node $node, $tstruct_next{$node}, $tstruct_prev{$node},";
1780       print " $tstruct_up{$node}\n\@unnumbered $title\n";
1781     } elsif ($ptype eq "appn") {
1782       # Appendix heading. Begin a new node.
1783       &texi_menu($node)
1784         if $tstruct_level{$tstruct_next{$node}} > $tstruct_level{$node};
1785       $pflags =~ /appn (.*) :(.*)/;
1786       $node = "Appendix $1";
1787       $title = "Appendix $1: ";
1788       foreach $i (@$pname) {
1789         $ww = &word_texi($i);
1790         $title .= $ww unless $ww eq "\001";
1791       }
1792       print "\@node $node, $tstruct_next{$node}, $tstruct_prev{$node},";
1793       print " $tstruct_up{$node}\n\@unnumbered $title\n";
1794     } elsif ($ptype eq "head" || $ptype eq "subh") {
1795       # Heading or subheading. Begin a new node.
1796       &texi_menu($node)
1797         if $tstruct_level{$tstruct_next{$node}} > $tstruct_level{$node};
1798       $pflags =~ /.... (.*) :(.*)/;
1799       $node = "Section $1";
1800       $title = "$1. ";
1801       foreach $i (@$pname) {
1802         $ww = &word_texi($i);
1803         $title .= $ww unless $ww eq "\001";
1804       }
1805       print "\@node $node, $tstruct_next{$node}, $tstruct_prev{$node},";
1806       print " $tstruct_up{$node}\n";
1807       $hdr = ($ptype eq "subh" ? "\@unnumberedsubsec" : "\@unnumberedsec");
1808       print "$hdr $title\n";
1809     } elsif ($ptype eq "code") {
1810       # Code paragraph. Surround with @example / @end example.
1811       print "\@example\n";
1812       foreach $i (@$pname) {
1813         warn "code line longer than 68 chars: $i\n" if length $i > 68;
1814         $i =~ s/\@/\@\@/g;
1815         $i =~ s/\{/\@\{/g;
1816         $i =~ s/\}/\@\}/g;
1817         print "$i\n";
1818       }
1819       print "\@end example\n";
1820     } elsif ($ptype eq "bull" || $ptype eq "norm") {
1821       # Ordinary paragraph, optionally bulleted. We wrap, FWIW.
1822       if ($ptype eq "bull") {
1823         $bulleting = 1, print "\@itemize \@bullet\n" if !$bulleting;
1824         print "\@item\n";
1825       }
1826       $line = '';
1827       @a = @$pname;
1828       $wd = $wprev = '';
1829       do {
1830         do { $w = &word_texi(shift @a); } while $w eq "\001"; # hack
1831         $wd .= $wprev;
1832         if ($wprev =~ /-$/ || $w eq ' ' || $w eq '' || $w eq undef) {
1833           if (length ($line . $wd) > 75) {
1834             $line =~ s/\s*$//; # trim trailing spaces
1835             print "$line\n";
1836             $line = '';
1837             $wd =~ s/^\s*//; # trim leading spaces
1838           }
1839           $line .= $wd;
1840           $wd = '';
1841         }
1842         $wprev = $w;
1843       } while ($w ne '' && $w ne undef);
1844       if ($line =~ /\S/) {
1845         $line =~ s/\s*$//; # trim trailing spaces
1846         print "$line\n";
1847       }
1848     }
1849   }
1850
1851   # Write index.
1852   &texi_index;
1853
1854   # Close file.
1855   print "\n\@contents\n\@bye\n";
1856   select STDOUT;
1857   close TEXT;
1858 }
1859
1860 # Side effect of this procedure: update global `texiwdlen' to be the length
1861 # in chars of the formatted version of the word.
1862 sub word_texi {
1863   my ($w) = @_;
1864   my $wtype, $wmajt;
1865
1866   return undef if $w eq '' || $w eq undef;
1867   $wtype = substr($w,0,2);
1868   $wmajt = substr($wtype,0,1);
1869   $w = substr($w,2);
1870   $wlen = length $w;
1871   $w =~ s/\@/\@\@/g;
1872   $w =~ s/\{/\@\{/g;
1873   $w =~ s/\}/\@\}/g;
1874   $w =~ s/<.*>// if $wmajt eq "w"; # remove web links
1875   substr($w,0,1) =~ tr/a-z/A-Z/, $capital = 0 if $capital;
1876   if ($wmajt eq "n" || $wtype eq "e " || $wtype eq "w ") {
1877     $texiwdlen = $wlen;
1878     return $w;
1879   } elsif ($wtype eq "sp") {
1880     $texiwdlen = 1;
1881     return ' ';
1882   } elsif ($wtype eq "da") {
1883     $texiwdlen = 2;
1884     return '--';
1885   } elsif ($wmajt eq "c" || $wtype eq "wc") {
1886     $texiwdlen = 2 + $wlen;
1887     return "\@code\{$w\}";
1888   } elsif ($wtype eq "es") {
1889     $texiwdlen = 1 + $wlen;
1890     return "\@emph\{${w}";
1891   } elsif ($wtype eq "ee") {
1892     $texiwdlen = 1 + $wlen;
1893     return "${w}\}";
1894   } elsif ($wtype eq "eo") {
1895     $texiwdlen = 2 + $wlen;
1896     return "\@emph\{${w}\}";
1897   } elsif ($wtype eq "x ") {
1898     $texiwdlen = 0; # we don't need it in this case
1899     $capital = 1; # hack
1900     return "\@ref\{";
1901   } elsif ($wtype eq "xe") {
1902     $texiwdlen = 0; # we don't need it in this case
1903     return "\}";
1904   } elsif ($wmajt eq "i") {
1905     $texiwdlen = 0; # we don't need it in this case
1906     return "\001";
1907   } else {
1908     die "panic in word_texi: $wtype$w\n";
1909   }
1910 }
1911
1912 sub texi_menu {
1913   my ($topitem) = @_;
1914   my $item, $i, $mpname, $title, $wd;
1915
1916   $item = $tstruct_next{$topitem};
1917   print "\@menu\n";
1918   while ($item) {
1919     $title = "";
1920     $mpname = $tstruct_pname{$item};
1921     foreach $i (@$mpname) {
1922       $wd = &word_texi($i);
1923       $title .= $wd unless $wd eq "\001";
1924     }
1925     print "* ${item}:: $title\n";
1926     $item = $tstruct_mnext{$item};
1927   }
1928   print "* Index::\n" if $topitem eq "Top";
1929   print "\@end menu\n";
1930 }
1931
1932 sub texi_index {
1933   my $itag, $ientry, @a, $wd, $item, $len;
1934   my $subnums = "123456789ABCDEFGHIJKLMNOPQRSTU" .
1935                 "VWXYZabcdefghijklmnopqrstuvwxyz";
1936
1937   print "\@ifinfo\n\@node Index, , $FIXMElastnode, Top\n";
1938   print "\@unnumbered Index\n\n\@menu\n";
1939
1940   foreach $itag (@itags) {
1941     $ientry = $idxmap{$itag};
1942     @a = @$ientry;
1943     $item = '';
1944     $len = 0;
1945     foreach $i (@a) {
1946       $wd = &word_texi($i);
1947       $item .= $wd, $len += $texiwdlen unless $wd eq "\001";
1948     }
1949     $i = 0;
1950     foreach $node (@nodes) {
1951       next if !$idxnodes{$node,$itag};
1952       printf "* %s%s (%s): %s.\n",
1953           $item, " " x (40-$len), substr($subnums,$i++,1), $node;
1954     }
1955   }
1956   print "\@end menu\n\@end ifinfo\n";
1957 }
1958
1959 sub write_hlp {
1960   # This is called from the top level, so I won't bother using
1961   # my or local.
1962
1963   # Build the index-tag text forms.
1964   print "building index entries...";
1965   @hlp_index = map {
1966                  my $i,$ww;
1967                  my $ientry = $idxmap{$_};
1968                  my $title = "";
1969                  foreach $i (@$ientry) {
1970                    $ww = &word_hlp($i,0);
1971                    $title .= $ww unless $ww eq "\001";
1972                  }
1973                  $title;
1974                } @itags;
1975
1976   # Write the HPJ project-description file.
1977   print "writing .hpj file...";
1978   open HPJ,">nasmdoc.hpj";
1979   print HPJ "[OPTIONS]\ncompress=true\n";
1980   print HPJ "title=NASM: The Netwide Assembler\noldkeyphrase=no\n\n";
1981   print HPJ "[FILES]\nnasmdoc.rtf\n\n";
1982   print HPJ "[CONFIG]\n";
1983   print HPJ 'CreateButton("btn_up", "&Up",'.
1984             ' "JumpContents(`nasmdoc.hlp'."'".')")';
1985   print HPJ "\nBrowseButtons()\n";
1986   close HPJ;
1987
1988   # Open file.
1989   print "\n   writing .rtf file...";
1990   open TEXT,">nasmdoc.rtf";
1991   select TEXT;
1992
1993   # Preamble.
1994   print "{\\rtf1\\ansi{\\fonttbl\n";
1995   print "\\f0\\froman Times New Roman;\\f1\\fmodern Courier New;\n";
1996   print "\\f2\\fswiss Arial;\\f3\\ftech Wingdings}\\deff0\n";
1997   print "#{\\footnote Top}\n";
1998   print "\${\\footnote Contents}\n";
1999   print "+{\\footnote browse:00000}\n";
2000   print "!{\\footnote DisableButton(\"btn_up\")}\n";
2001   print "\\keepn\\f2\\b\\fs30\\sb0\n";
2002   print "NASM: The Netwide Assembler\n";
2003   print "\\par\\pard\\plain\\sb120\n";
2004   print "This file documents NASM, the Netwide Assembler: an assembler \n";
2005   print "targetting the Intel x86 series of processors, with portable source.\n";
2006
2007   $node = "Top";
2008   $browse = 0;
2009
2010   $newpar = "\\par\\sb120\n";
2011   for ($para = 0; $para <= $#pnames; $para++) {
2012     $pname = $pnames[$para];
2013     $pflags = $pflags[$para];
2014     $ptype = substr($pflags,0,4);
2015
2016     print $newpar;
2017     $newpar = "\\par\\sb120\n";
2018
2019     if ($ptype eq "chap") {
2020       # Chapter heading. Begin a new node.
2021       &hlp_menu($node)
2022         if $tstruct_level{$tstruct_next{$node}} > $tstruct_level{$node};
2023       $pflags =~ /chap (.*) :(.*)/;
2024       $node = "Chapter $1";
2025       $title = $footnotetitle = "Chapter $1: ";
2026       foreach $i (@$pname) {
2027         $ww = &word_hlp($i,1);
2028         $title .= $ww, $footnotetitle .= &word_hlp($i,0) unless $ww eq "\001";
2029       }
2030       print "\\page\n";
2031       printf "#{\\footnote %s}\n", &hlp_sectkw($node);
2032       print "\${\\footnote $footnotetitle}\n";
2033       printf "+{\\footnote browse:%05d}\n", ++$browse;
2034       printf "!{\\footnote ChangeButtonBinding(\"btn_up\"," .
2035              "\"JumpId(\`nasmdoc.hlp',\`%s')\");\n",
2036              &hlp_sectkw($tstruct_up{$node});
2037       print "EnableButton(\"btn_up\")}\n";
2038       &hlp_keywords($node);
2039       print "\\keepn\\f2\\b\\fs30\\sb60\\sa60\n";
2040       print "$title\n";
2041       $newpar = "\\par\\pard\\plain\\sb120\n";
2042     } elsif ($ptype eq "appn") {
2043       # Appendix heading. Begin a new node.
2044       &hlp_menu($node)
2045         if $tstruct_level{$tstruct_next{$node}} > $tstruct_level{$node};
2046       $pflags =~ /appn (.*) :(.*)/;
2047       $node = "Appendix $1";
2048       $title = $footnotetitle = "Appendix $1: ";
2049       foreach $i (@$pname) {
2050         $ww = &word_hlp($i,1);
2051         $title .= $ww, $footnotetitle .= &word_hlp($i,0) unless $ww eq "\001";
2052       }
2053       print "\\page\n";
2054       printf "#{\\footnote %s}\n", &hlp_sectkw($node);
2055       print "\${\\footnote $footnotetitle}\n";
2056       printf "+{\\footnote browse:%05d}\n", ++$browse;
2057       printf "!{\\footnote ChangeButtonBinding(\"btn_up\"," .
2058              "\"JumpId(\`nasmdoc.hlp',\`%s')\");\n",
2059              &hlp_sectkw($tstruct_up{$node});
2060       print "EnableButton(\"btn_up\")}\n";
2061       &hlp_keywords($node);
2062       print "\\keepn\\f2\\b\\fs30\\sb60\\sa60\n";
2063       print "$title\n";
2064       $newpar = "\\par\\pard\\plain\\sb120\n";
2065     } elsif ($ptype eq "head" || $ptype eq "subh") {
2066       # Heading or subheading. Begin a new node.
2067       &hlp_menu($node)
2068         if $tstruct_level{$tstruct_next{$node}} > $tstruct_level{$node};
2069       $pflags =~ /.... (.*) :(.*)/;
2070       $node = "Section $1";
2071       $title = $footnotetitle = "$1. ";
2072       foreach $i (@$pname) {
2073         $ww = &word_hlp($i,1);
2074         $title .= $ww, $footnotetitle .= &word_hlp($i,0) unless $ww eq "\001";
2075       }
2076       print "\\page\n";
2077       printf "#{\\footnote %s}\n", &hlp_sectkw($node);
2078       print "\${\\footnote $footnotetitle}\n";
2079       printf "+{\\footnote browse:%05d}\n", ++$browse;
2080       printf "!{\\footnote ChangeButtonBinding(\"btn_up\"," .
2081              "\"JumpId(\`nasmdoc.hlp',\`%s')\");\n",
2082              &hlp_sectkw($tstruct_up{$node});
2083       print "EnableButton(\"btn_up\")}\n";
2084       &hlp_keywords($node);
2085       print "\\keepn\\f2\\b\\fs30\\sb60\\sa60\n";
2086       print "$title\n";
2087       $newpar = "\\par\\pard\\plain\\sb120\n";
2088     } elsif ($ptype eq "code") {
2089       # Code paragraph.
2090       print "\\keep\\f1\\sb120\n";
2091       foreach $i (@$pname) {
2092         warn "code line longer than 68 chars: $i\n" if length $i > 68;
2093         $i =~ s/\\/\\\\/g;
2094         $i =~ s/\{/\\\{/g;
2095         $i =~ s/\}/\\\}/g;
2096         print "$i\\par\\sb0\n";
2097       }
2098       $newpar = "\\pard\\f0\\sb120\n";
2099     } elsif ($ptype eq "bull" || $ptype eq "norm") {
2100       # Ordinary paragraph, optionally bulleted. We wrap, FWIW.
2101       if ($ptype eq "bull") {
2102         print "\\tx360\\li360\\fi-360{\\f3\\'9F}\\tab\n";
2103         $newpar = "\\par\\pard\\sb120\n";
2104       } else {
2105         $newpar = "\\par\\sb120\n";
2106       }
2107       $line = '';
2108       @a = @$pname;
2109       $wd = $wprev = '';
2110       do {
2111         do { $w = &word_hlp((shift @a),1); } while $w eq "\001"; # hack
2112         $wd .= $wprev;
2113         if ($w eq ' ' || $w eq '' || $w eq undef) {
2114           if (length ($line . $wd) > 75) {
2115             $line =~ s/\s*$//; # trim trailing spaces
2116             print "$line \n"; # and put one back
2117             $line = '';
2118             $wd =~ s/^\s*//; # trim leading spaces
2119           }
2120           $line .= $wd;
2121           $wd = '';
2122         }
2123         $wprev = $w;
2124       } while ($w ne '' && $w ne undef);
2125       if ($line =~ /\S/) {
2126         $line =~ s/\s*$//; # trim trailing spaces
2127         print "$line\n";
2128       }
2129     }
2130   }
2131
2132   # Close file.
2133   print "\\page}\n";
2134   select STDOUT;
2135   close TEXT;
2136 }
2137
2138 sub word_hlp {
2139   my ($w, $docode) = @_;
2140   my $wtype, $wmajt;
2141
2142   return undef if $w eq '' || $w eq undef;
2143   $wtype = substr($w,0,2);
2144   $wmajt = substr($wtype,0,1);
2145   $w = substr($w,2);
2146   $w =~ s/\\/\\\\/g;
2147   $w =~ s/\{/\\\{/g;
2148   $w =~ s/\}/\\\}/g;
2149   $w =~ s/<.*>// if $wmajt eq "w"; # remove web links
2150   substr($w,0,length($w)-1) =~ s/-/\\\'AD/g if $wmajt ne "x"; #nonbreakhyphens
2151   if ($wmajt eq "n" || $wtype eq "e " || $wtype eq "w ") {
2152     return $w;
2153   } elsif ($wtype eq "sp") {
2154     return ' ';
2155   } elsif ($wtype eq "da") {
2156     return "\\'96";
2157   } elsif ($wmajt eq "c" || $wtype eq "wc") {
2158     $w =~ s/ /\\\'A0/g; # make spaces non-breaking
2159     return $docode ? "{\\f1 ${w}}" : $w;
2160   } elsif ($wtype eq "es") {
2161     return "{\\i ${w}";
2162   } elsif ($wtype eq "ee") {
2163     return "${w}}";
2164   } elsif ($wtype eq "eo") {
2165     return "{\\i ${w}}";
2166   } elsif ($wtype eq "x ") {
2167     return "{\\uldb ";
2168   } elsif ($wtype eq "xe") {
2169     $w = &hlp_sectkw($w);
2170     return "}{\\v ${w}}";
2171   } elsif ($wmajt eq "i") {
2172     return "\001";
2173   } else {
2174     die "panic in word_hlp: $wtype$w\n";
2175   }
2176 }
2177
2178 sub hlp_menu {
2179   my ($topitem) = @_;
2180   my $item, $kword, $i, $mpname, $title;
2181
2182   $item = $tstruct_next{$topitem};
2183   print "\\li360\\fi-360\n";
2184   while ($item) {
2185     $title = "";
2186     $mpname = $tstruct_pname{$item};
2187     foreach $i (@$mpname) {
2188       $ww = &word_hlp($i, 0);
2189       $title .= $ww unless $ww eq "\001";
2190     }
2191     $kword = &hlp_sectkw($item);
2192     print "{\\uldb ${item}: $title}{\\v $kword}\\par\\sb0\n";
2193     $item = $tstruct_mnext{$item};
2194   }
2195   print "\\pard\\sb120\n";
2196 }
2197
2198 sub hlp_sectkw {
2199   my ($node) = @_;
2200   $node =~ tr/A-Z/a-z/;
2201   $node =~ tr/- ./___/;
2202   $node;
2203 }
2204
2205 sub hlp_keywords {
2206   my ($node) = @_;
2207   my $pfx = "K{\\footnote ";
2208   my $done = 0;
2209   foreach $i (0..$#itags) {
2210     (print $pfx,$hlp_index[$i]), $pfx = ";\n", $done++
2211         if $idxnodes{$node,$itags[$i]};
2212   }
2213   print "}\n" if $done;
2214 }
2215
2216 # Make tree structures. $tstruct_* is top-level and global.
2217 sub add_item {
2218   my ($item, $level) = @_;
2219   my $i;
2220
2221   $tstruct_pname{$item} = $pname;
2222   $tstruct_next{$tstruct_previtem} = $item;
2223   $tstruct_prev{$item} = $tstruct_previtem;
2224   $tstruct_level{$item} = $level;
2225   $tstruct_up{$item} = $tstruct_last[$level-1];
2226   $tstruct_mnext{$tstruct_last[$level]} = $item;
2227   $tstruct_last[$level] = $item;
2228   for ($i=$level+1; $i<$MAXLEVEL; $i++) { $tstruct_last[$i] = undef; }
2229   $tstruct_previtem = $item;
2230   push @nodes, $item;
2231 }
2232
2233 # PostScript font metric data. Used for line breaking.
2234 sub font_metrics {
2235   @timesr = (
2236      250,   0,   0,   0,   0,   0,   0,   0,
2237        0,   0,   0,   0,   0,   0,   0,   0,
2238        0,   0,   0,   0,   0,   0,   0,   0,
2239        0,   0,   0,   0,   0,   0,   0,   0,
2240      250, 333, 408, 500, 500, 833, 778, 333,
2241      333, 333, 500, 564, 250, 333, 250, 278,
2242      500, 500, 500, 500, 500, 500, 500, 500,
2243      500, 500, 278, 278, 564, 564, 564, 444,
2244      921, 722, 667, 667, 722, 611, 556, 722,
2245      722, 333, 389, 722, 611, 889, 722, 722,
2246      556, 722, 667, 556, 611, 722, 722, 944,
2247      722, 722, 611, 333, 278, 333, 469, 500,
2248      333, 444, 500, 444, 500, 444, 333, 500,
2249      500, 278, 278, 500, 278, 778, 500, 500,
2250      500, 500, 333, 389, 278, 500, 500, 722,
2251      500, 500, 444, 480, 200, 480, 541,   0,
2252        0,   0,   0,   0,   0,   0,   0,   0,
2253        0,   0,   0,   0,   0,   0,   0,   0,
2254        0,   0,   0,   0,   0,   0,   0,   0,
2255        0,   0,   0,   0,   0,   0,   0,   0,
2256        0, 333, 500, 500, 167, 500, 500, 500,
2257      500, 180, 444, 500, 333, 333, 556, 556,
2258        0, 500, 500, 500, 250,   0, 453, 350,
2259      333, 444, 444, 500,1000,1000,   0, 444,
2260        0, 333, 333, 333, 333, 333, 333, 333,
2261      333,   0, 333, 333,   0, 333, 333, 333,
2262     1000,   0,   0,   0,   0,   0,   0,   0,
2263        0,   0,   0,   0,   0,   0,   0,   0,
2264        0, 889,   0, 276,   0,   0,   0,   0,
2265      611, 722, 889, 310,   0,   0,   0,   0,
2266        0, 667,   0,   0,   0, 278,   0,   0,
2267      278, 500, 722, 500,   0,   0,   0,   0
2268   );
2269   @timesi = (
2270      250,   0,   0,   0,   0,   0,   0,   0,
2271        0,   0,   0,   0,   0,   0,   0,   0,
2272        0,   0,   0,   0,   0,   0,   0,   0,
2273        0,   0,   0,   0,   0,   0,   0,   0,
2274      250, 333, 420, 500, 500, 833, 778, 333,
2275      333, 333, 500, 675, 250, 333, 250, 278,
2276      500, 500, 500, 500, 500, 500, 500, 500,
2277      500, 500, 333, 333, 675, 675, 675, 500,
2278      920, 611, 611, 667, 722, 611, 611, 722,
2279      722, 333, 444, 667, 556, 833, 667, 722,
2280      611, 722, 611, 500, 556, 722, 611, 833,
2281      611, 556, 556, 389, 278, 389, 422, 500,
2282      333, 500, 500, 444, 500, 444, 278, 500,
2283      500, 278, 278, 444, 278, 722, 500, 500,
2284      500, 500, 389, 389, 278, 500, 444, 667,
2285      444, 444, 389, 400, 275, 400, 541,   0,
2286        0,   0,   0,   0,   0,   0,   0,   0,
2287        0,   0,   0,   0,   0,   0,   0,   0,
2288        0,   0,   0,   0,   0,   0,   0,   0,
2289        0,   0,   0,   0,   0,   0,   0,   0,
2290        0, 389, 500, 500, 167, 500, 500, 500,
2291      500, 214, 556, 500, 333, 333, 500, 500,
2292        0, 500, 500, 500, 250,   0, 523, 350,
2293      333, 556, 556, 500, 889,1000,   0, 500,
2294        0, 333, 333, 333, 333, 333, 333, 333,
2295      333,   0, 333, 333,   0, 333, 333, 333,
2296      889,   0,   0,   0,   0,   0,   0,   0,
2297        0,   0,   0,   0,   0,   0,   0,   0,
2298        0, 889,   0, 276,   0,   0,   0,   0,
2299      556, 722, 944, 310,   0,   0,   0,   0,
2300        0, 667,   0,   0,   0, 278,   0,   0,
2301      278, 500, 667, 500,   0,   0,   0,   0
2302   );
2303   @courr = (
2304      600,   0,   0,   0,   0,   0,   0,   0,
2305        0,   0,   0,   0,   0,   0,   0,   0,
2306        0,   0,   0,   0,   0,   0,   0,   0,
2307        0,   0,   0,   0,   0,   0,   0,   0,
2308      600, 600, 600, 600, 600, 600, 600, 600,
2309      600, 600, 600, 600, 600, 600, 600, 600,
2310      600, 600, 600, 600, 600, 600, 600, 600,
2311      600, 600, 600, 600, 600, 600, 600, 600,
2312      600, 600, 600, 600, 600, 600, 600, 600,
2313      600, 600, 600, 600, 600, 600, 600, 600,
2314      600, 600, 600, 600, 600, 600, 600, 600,
2315      600, 600, 600, 600, 600, 600, 600, 600,
2316      600, 600, 600, 600, 600, 600, 600, 600,
2317      600, 600, 600, 600, 600, 600, 600, 600,
2318      600, 600, 600, 600, 600, 600, 600, 600,
2319      600, 600, 600, 600, 600, 600, 600,   0,
2320        0,   0,   0,   0,   0,   0,   0,   0,
2321        0,   0,   0,   0,   0,   0,   0,   0,
2322        0,   0,   0,   0,   0,   0,   0,   0,
2323        0,   0,   0,   0,   0,   0,   0,   0,
2324        0, 600, 600, 600, 600, 600, 600, 600,
2325      600, 600, 600, 600, 600, 600, 600, 600,
2326        0, 600, 600, 600, 600,   0, 600, 600,
2327      600, 600, 600, 600, 600, 600,   0, 600,
2328        0, 600, 600, 600, 600, 600, 600, 600,
2329      600,   0, 600, 600,   0, 600, 600, 600,
2330      600,   0,   0,   0,   0,   0,   0,   0,
2331        0,   0,   0,   0,   0,   0,   0,   0,
2332        0, 600,   0, 600,   0,   0,   0,   0,
2333      600, 600, 600, 600,   0,   0,   0,   0,
2334        0, 600,   0,   0,   0, 600,   0,   0,
2335      600, 600, 600, 600,   0,   0,   0,   0
2336   );
2337 }