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