Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / roff / troff / div.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2004
3    Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22
23 // diversions
24
25 #include "troff.h"
26 #include "dictionary.h"
27 #include "hvunits.h"
28 #include "stringclass.h"
29 #include "mtsm.h"
30 #include "env.h"
31 #include "request.h"
32 #include "node.h"
33 #include "token.h"
34 #include "div.h"
35 #include "reg.h"
36
37 #include "nonposix.h"
38
39 int exit_started = 0;           // the exit process has started
40 int done_end_macro = 0;         // the end macro (if any) has finished
41 int seen_last_page_ejector = 0; // seen the LAST_PAGE_EJECTOR cookie
42 int last_page_number = 0;       // if > 0, the number of the last page
43                                 // specified with -o
44 static int began_page_in_end_macro = 0; // a new page was begun during the end macro
45
46 static int last_post_line_extra_space = 0; // needed for \n(.a
47 static int nl_reg_contents = -1;
48 static int dl_reg_contents = 0;
49 static int dn_reg_contents = 0;
50 static int vertical_position_traps_flag = 1;
51 static vunits truncated_space;
52 static vunits needed_space;
53
54 diversion::diversion(symbol s) 
55 : prev(0), nm(s), vertical_position(V0), high_water_mark(V0),
56   any_chars_added(0), no_space_mode(0), needs_push(0), saved_seen_break(0),
57   saved_seen_space(0), saved_seen_eol(0), saved_suppress_next_eol(0),
58   marked_place(V0)
59 {
60 }
61
62 struct vertical_size {
63   vunits pre_extra, post_extra, pre, post;
64   vertical_size(vunits vs, vunits post_vs);
65 };
66
67 vertical_size::vertical_size(vunits vs, vunits post_vs)
68 : pre_extra(V0), post_extra(V0), pre(vs), post(post_vs)
69 {
70 }
71
72 void node::set_vertical_size(vertical_size *)
73 {
74 }
75
76 void extra_size_node::set_vertical_size(vertical_size *v)
77 {
78   if (n < V0) {
79     if (-n > v->pre_extra)
80       v->pre_extra = -n;
81   }
82   else if (n > v->post_extra)
83     v->post_extra = n;
84 }
85
86 void vertical_size_node::set_vertical_size(vertical_size *v)
87 {
88   if (n < V0)
89     v->pre = -n;
90   else
91     v->post = n;
92 }
93
94 top_level_diversion *topdiv;
95
96 diversion *curdiv;
97
98 void do_divert(int append, int boxing)
99 {
100   tok.skip();
101   symbol nm = get_name();
102   if (nm.is_null()) {
103     if (curdiv->prev) {
104       curenv->seen_break = curdiv->saved_seen_break;
105       curenv->seen_space = curdiv->saved_seen_space;
106       curenv->seen_eol = curdiv->saved_seen_eol;
107       curenv->suppress_next_eol = curdiv->saved_suppress_next_eol;
108       if (boxing) {
109         curenv->line = curdiv->saved_line;
110         curenv->width_total = curdiv->saved_width_total;
111         curenv->space_total = curdiv->saved_space_total;
112         curenv->saved_indent = curdiv->saved_saved_indent;
113         curenv->target_text_length = curdiv->saved_target_text_length;
114         curenv->prev_line_interrupted = curdiv->saved_prev_line_interrupted;
115       }
116       diversion *temp = curdiv;
117       curdiv = curdiv->prev;
118       delete temp;
119     }
120     else
121       warning(WARN_DI, "diversion stack underflow");
122   }
123   else {
124     macro_diversion *md = new macro_diversion(nm, append);
125     md->prev = curdiv;
126     curdiv = md;
127     curdiv->saved_seen_break = curenv->seen_break;
128     curdiv->saved_seen_space = curenv->seen_space;
129     curdiv->saved_seen_eol = curenv->seen_eol;
130     curdiv->saved_suppress_next_eol = curenv->suppress_next_eol;
131     curenv->seen_break = 0;
132     curenv->seen_space = 0;
133     curenv->seen_eol = 0;
134     if (boxing) {
135       curdiv->saved_line = curenv->line;
136       curdiv->saved_width_total = curenv->width_total;
137       curdiv->saved_space_total = curenv->space_total;
138       curdiv->saved_saved_indent = curenv->saved_indent;
139       curdiv->saved_target_text_length = curenv->target_text_length;
140       curdiv->saved_prev_line_interrupted = curenv->prev_line_interrupted;
141       curenv->line = 0;
142       curenv->start_line();
143     }
144   }
145   skip_line();
146 }
147
148 void divert()
149 {
150   do_divert(0, 0);
151 }
152
153 void divert_append()
154 {
155   do_divert(1, 0);
156 }
157   
158 void box()
159 {
160   do_divert(0, 1);
161 }
162
163 void box_append()
164 {
165   do_divert(1, 1);
166 }
167
168 void diversion::need(vunits n)
169 {
170   vunits d = distance_to_next_trap();
171   if (d < n) {
172     truncated_space = -d;
173     needed_space = n;
174     space(d, 1);
175   }
176 }
177
178 macro_diversion::macro_diversion(symbol s, int append)
179 : diversion(s), max_width(H0)
180 {
181 #if 0
182   if (append) {
183     /* We don't allow recursive appends eg:
184
185       .da a
186       .a
187       .di
188       
189       This causes an infinite loop in troff anyway.
190       This is because the user could do
191
192       .as a foo
193
194       in the diversion, and this would mess things up royally,
195       since there would be two things appending to the same
196       macro_header.
197       To make it work, we would have to copy the _contents_
198       of the macro into which we were diverting; this doesn't
199       strike me as worthwhile.
200       However,
201
202       .di a
203       .a
204       .a
205       .di
206
207        will work and will make `a' contain two copies of what it contained
208        before; in troff, `a' would contain nothing. */
209     request_or_macro *rm 
210       = (request_or_macro *)request_dictionary.remove(s);
211     if (!rm || (mac = rm->to_macro()) == 0)
212       mac = new macro;
213   }
214   else
215     mac = new macro;
216 #endif
217   // We can now catch the situation described above by comparing
218   // the length of the charlist in the macro_header with the length
219   // stored in the macro. When we detect this, we copy the contents.
220   mac = new macro(1);
221   if (append) {
222     request_or_macro *rm 
223       = (request_or_macro *)request_dictionary.lookup(s);
224     if (rm) {
225       macro *m = rm->to_macro();
226       if (m)
227         *mac = *m;
228     }
229   }
230 }
231
232 macro_diversion::~macro_diversion()
233 {
234   request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
235   macro *m = rm ? rm->to_macro() : 0;
236   if (m) {
237     *m = *mac;
238     delete mac;
239   }
240   else
241     request_dictionary.define(nm, mac);
242   mac = 0;
243   dl_reg_contents = max_width.to_units();
244   dn_reg_contents = vertical_position.to_units();
245 }
246
247 vunits macro_diversion::distance_to_next_trap()
248 {
249   if (!diversion_trap.is_null() && diversion_trap_pos > vertical_position)
250     return diversion_trap_pos - vertical_position;
251   else
252     // Substract vresolution so that vunits::vunits does not overflow.
253     return vunits(INT_MAX - vresolution);
254 }
255
256 void macro_diversion::transparent_output(unsigned char c)
257 {
258   mac->append(c);
259 }
260
261 void macro_diversion::transparent_output(node *n)
262 {
263   mac->append(n);
264 }
265
266 void macro_diversion::output(node *nd, int retain_size,
267                              vunits vs, vunits post_vs, hunits width)
268 {
269   no_space_mode = 0;
270   vertical_size v(vs, post_vs);
271   while (nd != 0) {
272     nd->set_vertical_size(&v);
273     node *temp = nd;
274     nd = nd->next;
275     if (temp->interpret(mac))
276       delete temp;
277     else {
278 #if 1
279       temp->freeze_space();
280 #endif
281       mac->append(temp);
282     }
283   }
284   last_post_line_extra_space = v.post_extra.to_units();
285   if (!retain_size) {
286     v.pre = vs;
287     v.post = post_vs;
288   }
289   if (width > max_width)
290     max_width = width;
291   vunits x = v.pre + v.pre_extra + v.post + v.post_extra;
292   if (vertical_position_traps_flag
293       && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
294       && diversion_trap_pos <= vertical_position + x) {
295     vunits trunc = vertical_position + x - diversion_trap_pos;
296     if (trunc > v.post)
297       trunc = v.post;
298     v.post -= trunc;
299     x -= trunc;
300     truncated_space = trunc;
301     spring_trap(diversion_trap);
302   }
303   mac->append(new vertical_size_node(-v.pre));
304   mac->append(new vertical_size_node(v.post));
305   mac->append('\n');
306   vertical_position += x;
307   if (vertical_position - v.post > high_water_mark)
308     high_water_mark = vertical_position - v.post;
309 }
310
311 void macro_diversion::space(vunits n, int)
312 {
313   if (vertical_position_traps_flag
314       && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
315       && diversion_trap_pos <= vertical_position + n) {
316     truncated_space = vertical_position + n - diversion_trap_pos;
317     n = diversion_trap_pos - vertical_position;
318     spring_trap(diversion_trap);
319   }
320   else if (n + vertical_position < V0)
321     n = -vertical_position;
322   mac->append(new diverted_space_node(n));
323   vertical_position += n;
324 }
325
326 void macro_diversion::copy_file(const char *filename)
327 {
328   mac->append(new diverted_copy_file_node(filename));
329 }
330
331 top_level_diversion::top_level_diversion()
332 : page_number(0), page_count(0), last_page_count(-1),
333   page_length(units_per_inch*11),
334   prev_page_offset(units_per_inch), page_offset(units_per_inch),
335   page_trap_list(0), have_next_page_number(0),
336   ejecting_page(0), before_first_page(1)
337 {
338 }
339
340 // find the next trap after pos
341
342 trap *top_level_diversion::find_next_trap(vunits *next_trap_pos)
343 {
344   trap *next_trap = 0;
345   for (trap *pt = page_trap_list; pt != 0; pt = pt->next)
346     if (!pt->nm.is_null()) {
347       if (pt->position >= V0) {
348         if (pt->position > vertical_position 
349             && pt->position < page_length
350             && (next_trap == 0 || pt->position < *next_trap_pos)) {
351               next_trap = pt;
352               *next_trap_pos = pt->position;
353             }
354       }
355       else {
356         vunits pos = pt->position;
357         pos += page_length;
358         if (pos > 0 && pos > vertical_position && (next_trap == 0 || pos < *next_trap_pos)) {
359           next_trap = pt;
360           *next_trap_pos = pos;
361         }
362       }
363     }
364   return next_trap;
365 }
366
367 vunits top_level_diversion::distance_to_next_trap()
368 {
369   vunits d;
370   if (!find_next_trap(&d))
371     return page_length - vertical_position;
372   else
373     return d - vertical_position;
374 }
375
376 void top_level_diversion::output(node *nd, int retain_size,
377                                  vunits vs, vunits post_vs, hunits width)
378 {
379   no_space_mode = 0;
380   vunits next_trap_pos;
381   trap *next_trap = find_next_trap(&next_trap_pos);
382   if (before_first_page && begin_page()) 
383     fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
384   vertical_size v(vs, post_vs);
385   for (node *tem = nd; tem != 0; tem = tem->next)
386     tem->set_vertical_size(&v);
387   last_post_line_extra_space = v.post_extra.to_units();
388   if (!retain_size) {
389     v.pre = vs;
390     v.post = post_vs;
391   }
392   vertical_position += v.pre;
393   vertical_position += v.pre_extra;
394   the_output->print_line(page_offset, vertical_position, nd,
395                          v.pre + v.pre_extra, v.post_extra, width);
396   vertical_position += v.post_extra;
397   if (vertical_position > high_water_mark)
398     high_water_mark = vertical_position;
399   if (vertical_position_traps_flag && vertical_position >= page_length)
400     begin_page();
401   else if (vertical_position_traps_flag
402            && next_trap != 0 && vertical_position >= next_trap_pos) {
403     nl_reg_contents = vertical_position.to_units();
404     truncated_space = v.post;
405     spring_trap(next_trap->nm);
406   }
407   else if (v.post > V0) {
408     vertical_position += v.post;
409     if (vertical_position_traps_flag
410         && next_trap != 0 && vertical_position >= next_trap_pos) {
411       truncated_space = vertical_position - next_trap_pos;
412       vertical_position = next_trap_pos;
413       nl_reg_contents = vertical_position.to_units();
414       spring_trap(next_trap->nm);
415     }
416     else if (vertical_position_traps_flag && vertical_position >= page_length)
417       begin_page();
418     else
419       nl_reg_contents = vertical_position.to_units();
420   }
421   else
422     nl_reg_contents = vertical_position.to_units();
423 }
424
425 void top_level_diversion::transparent_output(unsigned char c)
426 {
427   if (before_first_page && begin_page())
428     // This can only happen with the .output request.
429     fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
430   const char *s = asciify(c);
431   while (*s)
432     the_output->transparent_char(*s++);
433 }
434
435 void top_level_diversion::transparent_output(node * /*n*/)
436 {
437   error("can't transparently output node at top level");
438 }
439
440 void top_level_diversion::copy_file(const char *filename)
441 {
442   if (before_first_page && begin_page())
443     fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
444   the_output->copy_file(page_offset, vertical_position, filename);
445 }
446
447 void top_level_diversion::space(vunits n, int forced)
448 {
449   if (no_space_mode) {
450     if (!forced)
451       return;
452     else
453       no_space_mode = 0;
454   }
455   if (before_first_page) {
456     begin_page(n);
457     return;
458   }
459   vunits next_trap_pos;
460   trap *next_trap = find_next_trap(&next_trap_pos);
461   vunits y = vertical_position + n;
462   if (curenv->get_vertical_spacing().to_units())
463     curenv->seen_space += n.to_units()
464                           / curenv->get_vertical_spacing().to_units();
465   if (vertical_position_traps_flag && next_trap != 0 && y >= next_trap_pos) {
466     vertical_position = next_trap_pos;
467     nl_reg_contents = vertical_position.to_units();
468     truncated_space = y - vertical_position;
469     spring_trap(next_trap->nm);
470   }
471   else if (y < V0) {
472     vertical_position = V0;
473     nl_reg_contents = vertical_position.to_units();
474   }
475   else if (vertical_position_traps_flag && y >= page_length && n >= V0)
476     begin_page(y - page_length);
477   else {
478     vertical_position = y;
479     nl_reg_contents = vertical_position.to_units();
480   }
481 }
482
483 trap::trap(symbol s, vunits n, trap *p)
484 : next(p), position(n), nm(s)
485 {
486 }
487
488 void top_level_diversion::add_trap(symbol nam, vunits pos)
489 {
490   trap *first_free_slot = 0;
491   trap **p;
492   for (p = &page_trap_list; *p; p = &(*p)->next) {
493     if ((*p)->nm.is_null()) {
494       if (first_free_slot == 0)
495         first_free_slot = *p;
496     }
497     else if ((*p)->position == pos) {
498       (*p)->nm = nam;
499       return;
500     }
501   }
502   if (first_free_slot) {
503     first_free_slot->nm = nam;
504     first_free_slot->position = pos;
505   }
506   else
507     *p = new trap(nam, pos, 0);
508 }  
509
510 void top_level_diversion::remove_trap(symbol nam)
511 {
512   for (trap *p = page_trap_list; p; p = p->next)
513     if (p->nm == nam) {
514       p->nm = NULL_SYMBOL;
515       return;
516     }
517 }
518
519 void top_level_diversion::remove_trap_at(vunits pos)
520 {
521   for (trap *p = page_trap_list; p; p = p->next)
522     if (p->position == pos) {
523       p->nm = NULL_SYMBOL;
524       return;
525     }
526 }
527       
528 void top_level_diversion::change_trap(symbol nam, vunits pos)
529 {
530   for (trap *p = page_trap_list; p; p = p->next)
531     if (p->nm == nam) {
532       p->position = pos;
533       return;
534     }
535 }
536
537 void top_level_diversion::print_traps()
538 {
539   for (trap *p = page_trap_list; p; p = p->next)
540     if (p->nm.is_null())
541       fprintf(stderr, "  empty\n");
542     else
543       fprintf(stderr, "%s\t%d\n", p->nm.contents(), p->position.to_units());
544   fflush(stderr);
545 }
546
547 void end_diversions()
548 {
549   while (curdiv != topdiv) {
550     error("automatically ending diversion `%1' on exit",
551             curdiv->nm.contents());
552     diversion *tem = curdiv;
553     curdiv = curdiv->prev;
554     delete tem;
555   }
556 }
557
558 void cleanup_and_exit(int exit_code)
559 {
560   if (the_output) {
561     the_output->trailer(topdiv->get_page_length());
562     delete the_output;
563   }
564   FLUSH_INPUT_PIPE(STDIN_FILENO);
565   exit(exit_code);
566 }
567
568 // Returns non-zero if it sprung a top-of-page trap.
569 // The optional parameter is for the .trunc register.
570 int top_level_diversion::begin_page(vunits n)
571 {
572   if (exit_started) {
573     if (page_count == last_page_count
574         ? curenv->is_empty()
575         : (done_end_macro && (seen_last_page_ejector || began_page_in_end_macro)))
576       cleanup_and_exit(0);
577     if (!done_end_macro)
578       began_page_in_end_macro = 1;
579   }
580   if (last_page_number > 0 && page_number == last_page_number)
581     cleanup_and_exit(0);
582   if (!the_output)
583     init_output();
584   ++page_count;
585   if (have_next_page_number) {
586     page_number = next_page_number;
587     have_next_page_number = 0;
588   }
589   else if (before_first_page == 1)
590     page_number = 1;
591   else
592     page_number++;
593   // spring the top of page trap if there is one
594   vunits next_trap_pos;
595   vertical_position = -vresolution;
596   trap *next_trap = find_next_trap(&next_trap_pos);
597   vertical_position = V0;
598   high_water_mark = V0;
599   ejecting_page = 0;
600   // If before_first_page was 2, then the top of page transition was undone
601   // using eg .nr nl 0-1.  See nl_reg::set_value.
602   if (before_first_page != 2)
603     the_output->begin_page(page_number, page_length);
604   before_first_page = 0;
605   nl_reg_contents = vertical_position.to_units();
606   if (vertical_position_traps_flag && next_trap != 0 && next_trap_pos == V0) {
607     truncated_space = n;
608     spring_trap(next_trap->nm);
609     return 1;
610   }
611   else
612     return 0;
613 }
614
615 void continue_page_eject()
616 {
617   if (topdiv->get_ejecting()) {
618     if (curdiv != topdiv)
619       error("can't continue page ejection because of current diversion");
620     else if (!vertical_position_traps_flag)
621       error("can't continue page ejection because vertical position traps disabled");
622     else {
623       push_page_ejector();
624       topdiv->space(topdiv->get_page_length(), 1);
625     }
626   }
627 }
628
629 void top_level_diversion::set_next_page_number(int n)
630 {
631   next_page_number= n;
632   have_next_page_number = 1;
633 }
634
635 int top_level_diversion::get_next_page_number()
636 {
637   return have_next_page_number ? next_page_number : page_number + 1;
638 }
639
640 void top_level_diversion::set_page_length(vunits n)
641 {
642   page_length = n;
643 }
644
645 diversion::~diversion()
646 {
647 }
648
649 void page_offset()
650 {
651   hunits n;
652   // The troff manual says that the default scaling indicator is v,
653   // but it is in fact m: v wouldn't make sense for a horizontally
654   // oriented request.
655   if (!has_arg() || !get_hunits(&n, 'm', topdiv->page_offset))
656     n = topdiv->prev_page_offset;
657   topdiv->prev_page_offset = topdiv->page_offset;
658   topdiv->page_offset = n;
659   topdiv->modified_tag.incl(MTSM_PO);
660   skip_line();
661 }
662
663 void page_length()
664 {
665   vunits n;
666   if (has_arg() && get_vunits(&n, 'v', topdiv->get_page_length()))
667     topdiv->set_page_length(n);
668   else
669     topdiv->set_page_length(11*units_per_inch);
670   skip_line();
671 }
672
673 void when_request()
674 {
675   vunits n;
676   if (get_vunits(&n, 'v')) {
677     symbol s = get_name();
678     if (s.is_null())
679       topdiv->remove_trap_at(n);
680     else
681       topdiv->add_trap(s, n);
682   }
683   skip_line();
684 }
685
686 void begin_page()
687 {
688   int got_arg = 0;
689   int n = 0;            /* pacify compiler */
690   if (has_arg() && get_integer(&n, topdiv->get_page_number()))
691     got_arg = 1;
692   while (!tok.newline() && !tok.eof())
693     tok.next();
694   if (curdiv == topdiv) {
695     if (topdiv->before_first_page) {
696       if (!break_flag) {
697         if (got_arg)
698           topdiv->set_next_page_number(n);
699         if (got_arg || !topdiv->no_space_mode)
700           topdiv->begin_page();
701       }
702       else if (topdiv->no_space_mode && !got_arg)
703         topdiv->begin_page();
704       else {
705         /* Given this
706
707          .wh 0 x
708          .de x
709          .tm \\n%
710          ..
711          .bp 3
712
713          troff prints
714
715          1
716          3
717
718          This code makes groff do the same. */
719
720         push_page_ejector();
721         topdiv->begin_page();
722         if (got_arg)
723           topdiv->set_next_page_number(n);
724         topdiv->set_ejecting();
725       }
726     }
727     else {
728       push_page_ejector();
729       if (break_flag)
730         curenv->do_break();
731       if (got_arg)
732         topdiv->set_next_page_number(n);
733       if (!(topdiv->no_space_mode && !got_arg))
734         topdiv->set_ejecting();
735     }
736   }
737   tok.next();
738 }
739
740 void no_space()
741 {
742   curdiv->no_space_mode = 1;
743   skip_line();
744 }
745
746 void restore_spacing()
747 {
748   curdiv->no_space_mode = 0;
749   skip_line();
750 }
751
752 /* It is necessary to generate a break before reading the argument,
753 because otherwise arguments using | will be wrong.  But if we just
754 generate a break as usual, then the line forced out may spring a trap
755 and thus push a macro onto the input stack before we have had a chance
756 to read the argument to the sp request.  We resolve this dilemma by
757 setting, before generating the break, a flag which will postpone the
758 actual pushing of the macro associated with the trap sprung by the
759 outputting of the line forced out by the break till after we have read
760 the argument to the request.  If the break did cause a trap to be
761 sprung, then we don't actually do the space. */
762
763 void space_request()
764 {
765   postpone_traps();
766   if (break_flag)
767     curenv->do_break();
768   vunits n;
769   if (!has_arg() || !get_vunits(&n, 'v'))
770     n = curenv->get_vertical_spacing();
771   while (!tok.newline() && !tok.eof())
772     tok.next();
773   if (!unpostpone_traps() && !curdiv->no_space_mode)
774     curdiv->space(n);
775   else
776     // The line might have had line spacing that was truncated.
777     truncated_space += n;
778   
779   tok.next();
780 }
781
782 void blank_line()
783 {
784   curenv->do_break();
785   if (!trap_sprung_flag && !curdiv->no_space_mode)
786     curdiv->space(curenv->get_vertical_spacing());
787   else
788     truncated_space += curenv->get_vertical_spacing();
789 }
790
791 /* need_space might spring a trap and so we must be careful that the
792 BEGIN_TRAP token is not skipped over. */
793
794 void need_space()
795 {
796   vunits n;
797   if (!has_arg() || !get_vunits(&n, 'v'))
798     n = curenv->get_vertical_spacing();
799   while (!tok.newline() && !tok.eof())
800     tok.next();
801   curdiv->need(n);
802   tok.next();
803 }
804
805 void page_number()
806 {
807   int n;
808
809   // the ps4html register is set if we are using -Tps
810   // to generate images for html
811   reg *r = (reg *)number_reg_dictionary.lookup("ps4html");
812   if (r == NULL)
813     if (has_arg() && get_integer(&n, topdiv->get_page_number()))
814       topdiv->set_next_page_number(n);
815   skip_line();
816 }
817
818 vunits saved_space;
819
820 void save_vertical_space()
821 {
822   vunits x;
823   if (!has_arg() || !get_vunits(&x, 'v'))
824     x = curenv->get_vertical_spacing();
825   if (curdiv->distance_to_next_trap() > x)
826     curdiv->space(x, 1);
827   else
828     saved_space = x;
829   skip_line();
830 }
831
832 void output_saved_vertical_space()
833 {
834   while (!tok.newline() && !tok.eof())
835     tok.next();
836   if (saved_space > V0)
837     curdiv->space(saved_space, 1);
838   saved_space = V0;
839   tok.next();
840 }
841
842 void flush_output()
843 {
844   while (!tok.newline() && !tok.eof())
845     tok.next();
846   if (break_flag)
847     curenv->do_break();
848   if (the_output)
849     the_output->flush();
850   tok.next();
851 }
852
853 void macro_diversion::set_diversion_trap(symbol s, vunits n)
854 {
855   diversion_trap = s;
856   diversion_trap_pos = n;
857 }
858
859 void macro_diversion::clear_diversion_trap()
860 {
861   diversion_trap = NULL_SYMBOL;
862 }
863
864 void top_level_diversion::set_diversion_trap(symbol, vunits)
865 {
866   error("can't set diversion trap when no current diversion");
867 }
868
869 void top_level_diversion::clear_diversion_trap()
870 {
871   error("can't set diversion trap when no current diversion");
872 }
873
874 void diversion_trap()
875 {
876   vunits n;
877   if (has_arg() && get_vunits(&n, 'v')) {
878     symbol s = get_name();
879     if (!s.is_null())
880       curdiv->set_diversion_trap(s, n);
881     else
882       curdiv->clear_diversion_trap();
883   }
884   else
885     curdiv->clear_diversion_trap();
886   skip_line();
887 }
888
889 void change_trap()
890 {
891   symbol s = get_name(1);
892   if (!s.is_null()) {
893     vunits x;
894     if (has_arg() && get_vunits(&x, 'v'))
895       topdiv->change_trap(s, x);
896     else
897       topdiv->remove_trap(s);
898   }
899   skip_line();
900 }
901
902 void print_traps()
903 {
904   topdiv->print_traps();
905   skip_line();
906 }
907
908 void mark()
909 {
910   symbol s = get_name();
911   if (s.is_null())
912     curdiv->marked_place = curdiv->get_vertical_position();
913   else if (curdiv == topdiv)
914     set_number_reg(s, nl_reg_contents);
915   else
916     set_number_reg(s, curdiv->get_vertical_position().to_units());
917   skip_line();
918 }
919
920 // This is truly bizarre.  It is documented in the SQ manual.
921
922 void return_request()
923 {
924   vunits dist = curdiv->marked_place - curdiv->get_vertical_position();
925   if (has_arg()) {
926     if (tok.ch() == '-') {
927       tok.next();
928       vunits x;
929       if (get_vunits(&x, 'v'))
930         dist = -x;
931     }
932     else {
933       vunits x;
934       if (get_vunits(&x, 'v'))
935         dist = x >= V0 ? x - curdiv->get_vertical_position() : V0;
936     }
937   }
938   if (dist < V0)
939     curdiv->space(dist);
940   skip_line();
941 }
942
943 void vertical_position_traps()
944 {
945   int n;
946   if (has_arg() && get_integer(&n))
947     vertical_position_traps_flag = (n != 0);
948   else
949     vertical_position_traps_flag = 1;
950   skip_line();
951 }
952
953 class page_offset_reg : public reg {
954 public:
955   int get_value(units *);
956   const char *get_string();
957 };
958   
959 int page_offset_reg::get_value(units *res)
960 {
961   *res = topdiv->get_page_offset().to_units();
962   return 1;
963 }
964
965 const char *page_offset_reg::get_string()
966 {
967   return i_to_a(topdiv->get_page_offset().to_units());
968 }
969
970 class page_length_reg : public reg {
971 public:
972   int get_value(units *);
973   const char *get_string();
974 };
975   
976 int page_length_reg::get_value(units *res)
977 {
978   *res = topdiv->get_page_length().to_units();
979   return 1;
980 }
981
982 const char *page_length_reg::get_string()
983 {
984   return i_to_a(topdiv->get_page_length().to_units());
985 }
986
987 class vertical_position_reg : public reg {
988 public:
989   int get_value(units *);
990   const char *get_string();
991 };
992   
993 int vertical_position_reg::get_value(units *res)
994 {
995   if (curdiv == topdiv && topdiv->before_first_page)
996     *res = -1;
997   else
998     *res = curdiv->get_vertical_position().to_units();
999   return 1;
1000 }
1001
1002 const char *vertical_position_reg::get_string()
1003 {
1004   if (curdiv == topdiv && topdiv->before_first_page)
1005     return "-1";
1006   else
1007     return i_to_a(curdiv->get_vertical_position().to_units());
1008 }
1009
1010 class high_water_mark_reg : public reg {
1011 public:
1012   int get_value(units *);
1013   const char *get_string();
1014 };
1015   
1016 int high_water_mark_reg::get_value(units *res)
1017 {
1018   *res = curdiv->get_high_water_mark().to_units();
1019   return 1;
1020 }
1021
1022 const char *high_water_mark_reg::get_string()
1023 {
1024   return i_to_a(curdiv->get_high_water_mark().to_units());
1025 }
1026
1027 class distance_to_next_trap_reg : public reg {
1028 public:
1029   int get_value(units *);
1030   const char *get_string();
1031 };
1032   
1033 int distance_to_next_trap_reg::get_value(units *res)
1034 {
1035   *res = curdiv->distance_to_next_trap().to_units();
1036   return 1;
1037 }
1038
1039 const char *distance_to_next_trap_reg::get_string()
1040 {
1041   return i_to_a(curdiv->distance_to_next_trap().to_units());
1042 }
1043
1044 class diversion_name_reg : public reg {
1045 public:
1046   const char *get_string();
1047 };
1048
1049 const char *diversion_name_reg::get_string()
1050 {
1051   return curdiv->get_diversion_name();
1052 }
1053
1054 class page_number_reg : public general_reg {
1055 public:
1056   page_number_reg();
1057   int get_value(units *);
1058   void set_value(units);
1059 };
1060
1061 page_number_reg::page_number_reg()
1062 {
1063 }
1064
1065 void page_number_reg::set_value(units n)
1066 {
1067   topdiv->set_page_number(n);
1068 }
1069
1070 int page_number_reg::get_value(units *res)
1071 {
1072   *res = topdiv->get_page_number();
1073   return 1;
1074 }
1075
1076 class next_page_number_reg : public reg {
1077 public:
1078   const char *get_string();
1079 };
1080
1081 const char *next_page_number_reg::get_string()
1082 {
1083   return i_to_a(topdiv->get_next_page_number());
1084 }
1085
1086 class page_ejecting_reg : public reg {
1087 public:
1088   const char *get_string();
1089 };
1090
1091 const char *page_ejecting_reg::get_string()
1092 {
1093   return i_to_a(topdiv->get_ejecting());
1094 }
1095
1096 class constant_vunits_reg : public reg {
1097   vunits *p;
1098 public:
1099   constant_vunits_reg(vunits *);
1100   const char *get_string();
1101 };
1102
1103 constant_vunits_reg::constant_vunits_reg(vunits *q) : p(q)
1104 {
1105 }
1106
1107 const char *constant_vunits_reg::get_string()
1108 {
1109   return i_to_a(p->to_units());
1110 }
1111
1112 class nl_reg : public variable_reg {
1113 public:
1114   nl_reg();
1115   void set_value(units);
1116 };
1117
1118 nl_reg::nl_reg() : variable_reg(&nl_reg_contents)
1119 {
1120 }
1121
1122 void nl_reg::set_value(units n)
1123 {
1124   variable_reg::set_value(n);
1125   // Setting nl to a negative value when the vertical position in
1126   // the top-level diversion is 0 undoes the top of page transition,
1127   // so that the header macro will be called as if the top of page
1128   // transition hasn't happened.  This is used by Larry Wall's
1129   // wrapman program.  Setting before_first_page to 2 rather than 1,
1130   // tells top_level_diversion::begin_page not to call
1131   // output_file::begin_page again.
1132   if (n < 0 && topdiv->get_vertical_position() == V0)
1133     topdiv->before_first_page = 2;
1134 }
1135
1136 class no_space_mode_reg : public reg {
1137 public:
1138   int get_value(units *);
1139   const char *get_string();
1140 };
1141
1142 int no_space_mode_reg::get_value(units *val)
1143 {
1144   *val = curdiv->no_space_mode;
1145   return 1;
1146 }
1147
1148 const char *no_space_mode_reg::get_string()
1149 {
1150   return curdiv->no_space_mode ? "1" : "0";
1151 }
1152
1153 void init_div_requests()
1154 {
1155   init_request("box", box);
1156   init_request("boxa", box_append);
1157   init_request("bp", begin_page);
1158   init_request("ch", change_trap);
1159   init_request("da", divert_append);
1160   init_request("di", divert);
1161   init_request("dt", diversion_trap);
1162   init_request("fl", flush_output);
1163   init_request("mk", mark);
1164   init_request("ne", need_space);
1165   init_request("ns", no_space);
1166   init_request("os", output_saved_vertical_space);
1167   init_request("pl", page_length);
1168   init_request("pn", page_number);
1169   init_request("po", page_offset);
1170   init_request("ptr", print_traps);
1171   init_request("rs", restore_spacing);
1172   init_request("rt", return_request);
1173   init_request("sp", space_request);
1174   init_request("sv", save_vertical_space);
1175   init_request("vpt", vertical_position_traps);
1176   init_request("wh", when_request);
1177   number_reg_dictionary.define(".a",
1178                        new constant_int_reg(&last_post_line_extra_space));
1179   number_reg_dictionary.define(".d", new vertical_position_reg);
1180   number_reg_dictionary.define(".h", new high_water_mark_reg);
1181   number_reg_dictionary.define(".ne",
1182                                new constant_vunits_reg(&needed_space));
1183   number_reg_dictionary.define(".ns", new no_space_mode_reg);
1184   number_reg_dictionary.define(".o", new page_offset_reg);
1185   number_reg_dictionary.define(".p", new page_length_reg);
1186   number_reg_dictionary.define(".pe", new page_ejecting_reg);
1187   number_reg_dictionary.define(".pn", new next_page_number_reg);
1188   number_reg_dictionary.define(".t", new distance_to_next_trap_reg);
1189   number_reg_dictionary.define(".trunc",
1190                                new constant_vunits_reg(&truncated_space));
1191   number_reg_dictionary.define(".vpt", 
1192                        new constant_int_reg(&vertical_position_traps_flag));
1193   number_reg_dictionary.define(".z", new diversion_name_reg);
1194   number_reg_dictionary.define("dl", new variable_reg(&dl_reg_contents));
1195   number_reg_dictionary.define("dn", new variable_reg(&dn_reg_contents));
1196   number_reg_dictionary.define("nl", new nl_reg);
1197   number_reg_dictionary.define("%", new page_number_reg);
1198 }