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