bf62d6283946ff8f01a8b045d2fe78cf26431cda
[platform/upstream/groff.git] / src / roff / troff / div.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2014  Free Software Foundation, Inc.
3      Written by James Clark (jjc@jclark.com)
4
5 This file is part of groff.
6
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20
21 // diversions
22
23 #include "troff.h"
24 #include "dictionary.h"
25 #include "hvunits.h"
26 #include "stringclass.h"
27 #include "mtsm.h"
28 #include "env.h"
29 #include "request.h"
30 #include "node.h"
31 #include "token.h"
32 #include "div.h"
33 #include "reg.h"
34
35 #include "nonposix.h"
36
37 int exit_started = 0;           // the exit process has started
38 int done_end_macro = 0;         // the end macro (if any) has finished
39 int seen_last_page_ejector = 0; // seen the LAST_PAGE_EJECTOR cookie
40 int last_page_number = 0;       // if > 0, the number of the last page
41                                 // specified with -o
42 static int began_page_in_end_macro = 0; // a new page was begun during the end macro
43
44 static int last_post_line_extra_space = 0; // needed for \n(.a
45 static int nl_reg_contents = -1;
46 static int dl_reg_contents = 0;
47 static int dn_reg_contents = 0;
48 static int vertical_position_traps_flag = 1;
49 static vunits truncated_space;
50 static vunits needed_space;
51
52 diversion::diversion(symbol s) 
53 : prev(0), nm(s), vertical_position(V0), high_water_mark(V0),
54   any_chars_added(0), no_space_mode(0), needs_push(0), saved_seen_break(0),
55   saved_seen_space(0), saved_seen_eol(0), saved_suppress_next_eol(0),
56   marked_place(V0)
57 {
58 }
59
60 struct vertical_size {
61   vunits pre_extra, post_extra, pre, post;
62   vertical_size(vunits vs, vunits post_vs);
63 };
64
65 vertical_size::vertical_size(vunits vs, vunits post_vs)
66 : pre_extra(V0), post_extra(V0), pre(vs), post(post_vs)
67 {
68 }
69
70 void node::set_vertical_size(vertical_size *)
71 {
72 }
73
74 void extra_size_node::set_vertical_size(vertical_size *v)
75 {
76   if (n < V0) {
77     if (-n > v->pre_extra)
78       v->pre_extra = -n;
79   }
80   else if (n > v->post_extra)
81     v->post_extra = n;
82 }
83
84 void vertical_size_node::set_vertical_size(vertical_size *v)
85 {
86   if (n < V0)
87     v->pre = -n;
88   else
89     v->post = n;
90 }
91
92 top_level_diversion *topdiv;
93
94 diversion *curdiv;
95
96 void do_divert(int append, int boxing)
97 {
98   tok.skip();
99   symbol nm = get_name();
100   if (nm.is_null()) {
101     if (curdiv->prev) {
102       curenv->seen_break = curdiv->saved_seen_break;
103       curenv->seen_space = curdiv->saved_seen_space;
104       curenv->seen_eol = curdiv->saved_seen_eol;
105       curenv->suppress_next_eol = curdiv->saved_suppress_next_eol;
106       if (boxing) {
107         curenv->line = curdiv->saved_line;
108         curenv->width_total = curdiv->saved_width_total;
109         curenv->space_total = curdiv->saved_space_total;
110         curenv->saved_indent = curdiv->saved_saved_indent;
111         curenv->target_text_length = curdiv->saved_target_text_length;
112         curenv->prev_line_interrupted = curdiv->saved_prev_line_interrupted;
113       }
114       diversion *temp = curdiv;
115       curdiv = curdiv->prev;
116       delete temp;
117     }
118     else
119       warning(WARN_DI, "diversion stack underflow");
120   }
121   else {
122     macro_diversion *md = new macro_diversion(nm, append);
123     md->prev = curdiv;
124     curdiv = md;
125     curdiv->saved_seen_break = curenv->seen_break;
126     curdiv->saved_seen_space = curenv->seen_space;
127     curdiv->saved_seen_eol = curenv->seen_eol;
128     curdiv->saved_suppress_next_eol = curenv->suppress_next_eol;
129     curenv->seen_break = 0;
130     curenv->seen_space = 0;
131     curenv->seen_eol = 0;
132     if (boxing) {
133       curdiv->saved_line = curenv->line;
134       curdiv->saved_width_total = curenv->width_total;
135       curdiv->saved_space_total = curenv->space_total;
136       curdiv->saved_saved_indent = curenv->saved_indent;
137       curdiv->saved_target_text_length = curenv->target_text_length;
138       curdiv->saved_prev_line_interrupted = curenv->prev_line_interrupted;
139       curenv->line = 0;
140       curenv->start_line();
141     }
142   }
143   skip_line();
144 }
145
146 void divert()
147 {
148   do_divert(0, 0);
149 }
150
151 void divert_append()
152 {
153   do_divert(1, 0);
154 }
155   
156 void box()
157 {
158   do_divert(0, 1);
159 }
160
161 void box_append()
162 {
163   do_divert(1, 1);
164 }
165
166 void diversion::need(vunits n)
167 {
168   vunits d = distance_to_next_trap();
169   if (d < n) {
170     truncated_space = -d;
171     needed_space = n;
172     space(d, 1);
173   }
174 }
175
176 macro_diversion::macro_diversion(symbol s, int append)
177 : diversion(s), max_width(H0)
178 {
179 #if 0
180   if (append) {
181     /* We don't allow recursive appends eg:
182
183       .da a
184       .a
185       .di
186       
187       This causes an infinite loop in troff anyway.
188       This is because the user could do
189
190       .as a foo
191
192       in the diversion, and this would mess things up royally,
193       since there would be two things appending to the same
194       macro_header.
195       To make it work, we would have to copy the _contents_
196       of the macro into which we were diverting; this doesn't
197       strike me as worthwhile.
198       However,
199
200       .di a
201       .a
202       .a
203       .di
204
205        will work and will make `a' contain two copies of what it contained
206        before; in troff, `a' would contain nothing. */
207     request_or_macro *rm 
208       = (request_or_macro *)request_dictionary.remove(s);
209     if (!rm || (mac = rm->to_macro()) == 0)
210       mac = new macro;
211   }
212   else
213     mac = new macro;
214 #endif
215   // We can now catch the situation described above by comparing
216   // the length of the charlist in the macro_header with the length
217   // stored in the macro. When we detect this, we copy the contents.
218   mac = new macro(1);
219   if (append) {
220     request_or_macro *rm 
221       = (request_or_macro *)request_dictionary.lookup(s);
222     if (rm) {
223       macro *m = rm->to_macro();
224       if (m)
225         *mac = *m;
226     }
227   }
228 }
229
230 macro_diversion::~macro_diversion()
231 {
232   request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
233   macro *m = rm ? rm->to_macro() : 0;
234   if (m) {
235     *m = *mac;
236     delete mac;
237   }
238   else
239     request_dictionary.define(nm, mac);
240   mac = 0;
241   dl_reg_contents = max_width.to_units();
242   dn_reg_contents = vertical_position.to_units();
243 }
244
245 vunits macro_diversion::distance_to_next_trap()
246 {
247   if (!diversion_trap.is_null() && diversion_trap_pos > vertical_position)
248     return diversion_trap_pos - vertical_position;
249   else
250     // Substract vresolution so that vunits::vunits does not overflow.
251     return vunits(INT_MAX - vresolution);
252 }
253
254 void macro_diversion::transparent_output(unsigned char c)
255 {
256   mac->append(c);
257 }
258
259 void macro_diversion::transparent_output(node *n)
260 {
261   mac->append(n);
262 }
263
264 void macro_diversion::output(node *nd, int retain_size,
265                              vunits vs, vunits post_vs, hunits width)
266 {
267   no_space_mode = 0;
268   vertical_size v(vs, post_vs);
269   while (nd != 0) {
270     nd->set_vertical_size(&v);
271     node *temp = nd;
272     nd = nd->next;
273     if (temp->interpret(mac))
274       delete temp;
275     else {
276 #if 1
277       temp->freeze_space();
278 #endif
279       mac->append(temp);
280     }
281   }
282   last_post_line_extra_space = v.post_extra.to_units();
283   if (!retain_size) {
284     v.pre = vs;
285     v.post = post_vs;
286   }
287   if (width > max_width)
288     max_width = width;
289   vunits x = v.pre + v.pre_extra + v.post + v.post_extra;
290   if (vertical_position_traps_flag
291       && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
292       && diversion_trap_pos <= vertical_position + x) {
293     vunits trunc = vertical_position + x - diversion_trap_pos;
294     if (trunc > v.post)
295       trunc = v.post;
296     v.post -= trunc;
297     x -= trunc;
298     truncated_space = trunc;
299     spring_trap(diversion_trap);
300   }
301   mac->append(new vertical_size_node(-v.pre));
302   mac->append(new vertical_size_node(v.post));
303   mac->append('\n');
304   vertical_position += x;
305   if (vertical_position - v.post > high_water_mark)
306     high_water_mark = vertical_position - v.post;
307 }
308
309 void macro_diversion::space(vunits n, int)
310 {
311   if (vertical_position_traps_flag
312       && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
313       && diversion_trap_pos <= vertical_position + n) {
314     truncated_space = vertical_position + n - diversion_trap_pos;
315     n = diversion_trap_pos - vertical_position;
316     spring_trap(diversion_trap);
317   }
318   else if (n + vertical_position < V0)
319     n = -vertical_position;
320   mac->append(new diverted_space_node(n));
321   vertical_position += n;
322 }
323
324 void macro_diversion::copy_file(const char *filename)
325 {
326   mac->append(new diverted_copy_file_node(filename));
327 }
328
329 top_level_diversion::top_level_diversion()
330 : page_number(0), page_count(0), last_page_count(-1),
331   page_length(units_per_inch*11),
332   prev_page_offset(units_per_inch), page_offset(units_per_inch),
333   page_trap_list(0), have_next_page_number(0),
334   ejecting_page(0), before_first_page(1)
335 {
336 }
337
338 // find the next trap after pos
339
340 trap *top_level_diversion::find_next_trap(vunits *next_trap_pos)
341 {
342   trap *next_trap = 0;
343   for (trap *pt = page_trap_list; pt != 0; pt = pt->next)
344     if (!pt->nm.is_null()) {
345       if (pt->position >= V0) {
346         if (pt->position > vertical_position 
347             && pt->position < page_length
348             && (next_trap == 0 || pt->position < *next_trap_pos)) {
349           next_trap = pt;
350           *next_trap_pos = pt->position;
351         }
352       }
353       else {
354         vunits pos = pt->position;
355         pos += page_length;
356         if (pos > 0
357             && pos > vertical_position
358             && (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 }