div.cpp revision 114402
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
6This file is part of groff.
7
8groff is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 2, or (at your option) any later
11version.
12
13groff is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16for more details.
17
18You should have received a copy of the GNU General Public License along
19with groff; see the file COPYING.  If not, write to the Free Software
20Foundation, 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
36int exit_started = 0;		// the exit process has started
37int done_end_macro = 0;		// the end macro (if any) has finished
38int seen_last_page_ejector = 0;	// seen the LAST_PAGE_EJECTOR cookie
39int last_page_number = 0;	// if > 0, the number of the last page
40				// specified with -o
41static int began_page_in_end_macro = 0;	// a new page was begun during the end macro
42
43static int last_post_line_extra_space = 0; // needed for \n(.a
44static int nl_reg_contents = -1;
45static int dl_reg_contents = 0;
46static int dn_reg_contents = 0;
47static int vertical_position_traps_flag = 1;
48static vunits truncated_space;
49static vunits needed_space;
50
51diversion::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
57struct vertical_size {
58  vunits pre_extra, post_extra, pre, post;
59  vertical_size(vunits vs, vunits post_vs);
60};
61
62vertical_size::vertical_size(vunits vs, vunits post_vs)
63: pre_extra(V0), post_extra(V0), pre(vs), post(post_vs)
64{
65}
66
67void node::set_vertical_size(vertical_size *)
68{
69}
70
71void 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
81void 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
89top_level_diversion *topdiv;
90
91diversion *curdiv;
92
93void 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
132void divert()
133{
134  do_divert(0, 0);
135}
136
137void divert_append()
138{
139  do_divert(1, 0);
140}
141
142void box()
143{
144  do_divert(0, 1);
145}
146
147void box_append()
148{
149  do_divert(1, 1);
150}
151
152void diversion::need(vunits n)
153{
154  vunits d = distance_to_next_trap();
155  if (d < n) {
156    truncated_space = -d;
157    needed_space = n;
158    space(d, 1);
159  }
160}
161
162macro_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
216macro_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
231vunits 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
240void macro_diversion::transparent_output(unsigned char c)
241{
242  mac->append(c);
243}
244
245void macro_diversion::transparent_output(node *n)
246{
247  mac->append(n);
248}
249
250void 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
296void 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
311void macro_diversion::copy_file(const char *filename)
312{
313  mac->append(new diverted_copy_file_node(filename));
314}
315
316top_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
327trap *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
352vunits 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
361void 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
410void 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
420void top_level_diversion::transparent_output(node * /*n*/)
421{
422  error("can't transparently output node at top level");
423}
424
425void 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
432void 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    begin_page(n);
442    return;
443  }
444  vunits next_trap_pos;
445  trap *next_trap = find_next_trap(&next_trap_pos);
446  vunits y = vertical_position + n;
447  if (vertical_position_traps_flag && next_trap != 0 && y >= next_trap_pos) {
448    vertical_position = next_trap_pos;
449    nl_reg_contents = vertical_position.to_units();
450    truncated_space = y - vertical_position;
451    spring_trap(next_trap->nm);
452  }
453  else if (y < V0) {
454    vertical_position = V0;
455    nl_reg_contents = vertical_position.to_units();
456  }
457  else if (vertical_position_traps_flag && y >= page_length && n >= V0)
458    begin_page(y - page_length);
459  else {
460    vertical_position = y;
461    nl_reg_contents = vertical_position.to_units();
462  }
463}
464
465trap::trap(symbol s, vunits n, trap *p)
466: next(p), position(n), nm(s)
467{
468}
469
470void top_level_diversion::add_trap(symbol nm, vunits pos)
471{
472  trap *first_free_slot = 0;
473  trap **p;
474  for (p = &page_trap_list; *p; p = &(*p)->next) {
475    if ((*p)->nm.is_null()) {
476      if (first_free_slot == 0)
477	first_free_slot = *p;
478    }
479    else if ((*p)->position == pos) {
480      (*p)->nm = nm;
481      return;
482    }
483  }
484  if (first_free_slot) {
485    first_free_slot->nm = nm;
486    first_free_slot->position = pos;
487  }
488  else
489    *p = new trap(nm, pos, 0);
490}
491
492void top_level_diversion::remove_trap(symbol nm)
493{
494  for (trap *p = page_trap_list; p; p = p->next)
495    if (p->nm == nm) {
496      p->nm = NULL_SYMBOL;
497      return;
498    }
499}
500
501void top_level_diversion::remove_trap_at(vunits pos)
502{
503  for (trap *p = page_trap_list; p; p = p->next)
504    if (p->position == pos) {
505      p->nm = NULL_SYMBOL;
506      return;
507    }
508}
509
510void top_level_diversion::change_trap(symbol nm, vunits pos)
511{
512  for (trap *p = page_trap_list; p; p = p->next)
513    if (p->nm == nm) {
514      p->position = pos;
515      return;
516    }
517}
518
519void top_level_diversion::print_traps()
520{
521  for (trap *p = page_trap_list; p; p = p->next)
522    if (p->nm.is_null())
523      fprintf(stderr, "  empty\n");
524    else
525      fprintf(stderr, "%s\t%d\n", p->nm.contents(), p->position.to_units());
526  fflush(stderr);
527}
528
529void end_diversions()
530{
531  while (curdiv != topdiv) {
532    error("automatically ending diversion `%1' on exit",
533	    curdiv->nm.contents());
534    diversion *tem = curdiv;
535    curdiv = curdiv->prev;
536    delete tem;
537  }
538}
539
540void cleanup_and_exit(int exit_code)
541{
542  if (the_output) {
543    the_output->trailer(topdiv->get_page_length());
544    delete the_output;
545  }
546  exit(exit_code);
547}
548
549// Returns non-zero if it sprung a top-of-page trap.
550// The optional parameter is for the .trunc register.
551int top_level_diversion::begin_page(vunits n)
552{
553  if (exit_started) {
554    if (page_count == last_page_count
555	? curenv->is_empty()
556	: (done_end_macro && (seen_last_page_ejector || began_page_in_end_macro)))
557      cleanup_and_exit(0);
558    if (!done_end_macro)
559      began_page_in_end_macro = 1;
560  }
561  if (last_page_number > 0 && page_number == last_page_number)
562    cleanup_and_exit(0);
563  if (!the_output)
564    init_output();
565  ++page_count;
566  if (have_next_page_number) {
567    page_number = next_page_number;
568    have_next_page_number = 0;
569  }
570  else if (before_first_page == 1)
571    page_number = 1;
572  else
573    page_number++;
574  // spring the top of page trap if there is one
575  vunits next_trap_pos;
576  vertical_position = -vresolution;
577  trap *next_trap = find_next_trap(&next_trap_pos);
578  vertical_position = V0;
579  high_water_mark = V0;
580  ejecting_page = 0;
581  // If before_first_page was 2, then the top of page transition was undone
582  // using eg .nr nl 0-1.  See nl_reg::set_value.
583  if (before_first_page != 2)
584    the_output->begin_page(page_number, page_length);
585  before_first_page = 0;
586  nl_reg_contents = vertical_position.to_units();
587  if (vertical_position_traps_flag && next_trap != 0 && next_trap_pos == V0) {
588    truncated_space = n;
589    spring_trap(next_trap->nm);
590    return 1;
591  }
592  else
593    return 0;
594}
595
596void continue_page_eject()
597{
598  if (topdiv->get_ejecting()) {
599    if (curdiv != topdiv)
600      error("can't continue page ejection because of current diversion");
601    else if (!vertical_position_traps_flag)
602      error("can't continue page ejection because vertical position traps disabled");
603    else {
604      push_page_ejector();
605      topdiv->space(topdiv->get_page_length(), 1);
606    }
607  }
608}
609
610void top_level_diversion::set_next_page_number(int n)
611{
612  next_page_number= n;
613  have_next_page_number = 1;
614}
615
616int top_level_diversion::get_next_page_number()
617{
618  return have_next_page_number ? next_page_number : page_number + 1;
619}
620
621void top_level_diversion::set_page_length(vunits n)
622{
623  page_length = n;
624}
625
626diversion::~diversion()
627{
628}
629
630void page_offset()
631{
632  hunits n;
633  // The troff manual says that the default scaling indicator is v,
634  // but it is in fact m: v wouldn't make sense for a horizontally
635  // oriented request.
636  if (!has_arg() || !get_hunits(&n, 'm', topdiv->page_offset))
637    n = topdiv->prev_page_offset;
638  topdiv->prev_page_offset = topdiv->page_offset;
639  topdiv->page_offset = n;
640  curenv->add_html_tag(0, ".po", n.to_units());
641  skip_line();
642}
643
644void page_length()
645{
646  vunits n;
647  if (has_arg() && get_vunits(&n, 'v', topdiv->get_page_length()))
648    topdiv->set_page_length(n);
649  else
650    topdiv->set_page_length(11*units_per_inch);
651  skip_line();
652}
653
654void when_request()
655{
656  vunits n;
657  if (get_vunits(&n, 'v')) {
658    symbol s = get_name();
659    if (s.is_null())
660      topdiv->remove_trap_at(n);
661    else
662      topdiv->add_trap(s, n);
663  }
664  skip_line();
665}
666
667void begin_page()
668{
669  int got_arg = 0;
670  int n;
671  if (has_arg() && get_integer(&n, topdiv->get_page_number()))
672    got_arg = 1;
673  while (!tok.newline() && !tok.eof())
674    tok.next();
675  if (curdiv == topdiv) {
676    if (topdiv->before_first_page) {
677      if (!break_flag) {
678	if (got_arg)
679	  topdiv->set_next_page_number(n);
680	if (got_arg || !topdiv->no_space_mode)
681	  topdiv->begin_page();
682      }
683      else if (topdiv->no_space_mode && !got_arg)
684	topdiv->begin_page();
685      else {
686	/* Given this
687
688         .wh 0 x
689	 .de x
690	 .tm \\n%
691	 ..
692	 .bp 3
693
694	 troff prints
695
696	 1
697	 3
698
699	 This code makes groff do the same. */
700
701	push_page_ejector();
702	topdiv->begin_page();
703	if (got_arg)
704	  topdiv->set_next_page_number(n);
705	topdiv->set_ejecting();
706      }
707    }
708    else {
709      push_page_ejector();
710      if (break_flag)
711	curenv->do_break();
712      if (got_arg)
713	topdiv->set_next_page_number(n);
714      if (!(topdiv->no_space_mode && !got_arg))
715	topdiv->set_ejecting();
716    }
717  }
718  tok.next();
719}
720
721void no_space()
722{
723  curdiv->no_space_mode = 1;
724  skip_line();
725}
726
727void restore_spacing()
728{
729  curdiv->no_space_mode = 0;
730  skip_line();
731}
732
733/* It is necessary to generate a break before before reading the argument,
734because otherwise arguments using | will be wrong. But if we just
735generate a break as usual, then the line forced out may spring a trap
736and thus push a macro onto the input stack before we have had a chance
737to read the argument to the sp request. We resolve this dilemma by
738setting, before generating the break, a flag which will postpone the
739actual pushing of the macro associated with the trap sprung by the
740outputting of the line forced out by the break till after we have read
741the argument to the request.  If the break did cause a trap to be
742sprung, then we don't actually do the space. */
743
744void space_request()
745{
746  postpone_traps();
747  if (break_flag)
748    curenv->do_break();
749  vunits n;
750  if (!has_arg() || !get_vunits(&n, 'v'))
751    n = curenv->get_vertical_spacing();
752  while (!tok.newline() && !tok.eof())
753    tok.next();
754  if (!unpostpone_traps() && !curdiv->no_space_mode)
755    curdiv->space(n);
756  else
757    // The line might have had line spacing that was truncated.
758    truncated_space += n;
759  curenv->add_html_tag(1, ".sp", n.to_units());
760  tok.next();
761}
762
763void blank_line()
764{
765  curenv->do_break();
766  if (!trap_sprung_flag && !curdiv->no_space_mode) {
767    curdiv->space(curenv->get_vertical_spacing());
768    curenv->add_html_tag(1, ".sp", 1);
769  }
770  else
771    truncated_space += curenv->get_vertical_spacing();
772}
773
774/* need_space might spring a trap and so we must be careful that the
775BEGIN_TRAP token is not skipped over. */
776
777void need_space()
778{
779  vunits n;
780  if (!has_arg() || !get_vunits(&n, 'v'))
781    n = curenv->get_vertical_spacing();
782  while (!tok.newline() && !tok.eof())
783    tok.next();
784  curdiv->need(n);
785  tok.next();
786}
787
788void page_number()
789{
790  int n;
791
792  // the ps4html register is set if we are using -Tps
793  // to generate images for html
794  reg *r = (reg *)number_reg_dictionary.lookup("ps4html");
795  if (r == NULL)
796    if (has_arg() && get_integer(&n, topdiv->get_page_number()))
797      topdiv->set_next_page_number(n);
798  skip_line();
799}
800
801vunits saved_space;
802
803void save_vertical_space()
804{
805  vunits x;
806  if (!has_arg() || !get_vunits(&x, 'v'))
807    x = curenv->get_vertical_spacing();
808  if (curdiv->distance_to_next_trap() > x)
809    curdiv->space(x, 1);
810  else
811    saved_space = x;
812  skip_line();
813}
814
815void output_saved_vertical_space()
816{
817  while (!tok.newline() && !tok.eof())
818    tok.next();
819  if (saved_space > V0)
820    curdiv->space(saved_space, 1);
821  saved_space = V0;
822  tok.next();
823}
824
825void flush_output()
826{
827  while (!tok.newline() && !tok.eof())
828    tok.next();
829  if (break_flag)
830    curenv->do_break();
831  if (the_output)
832    the_output->flush();
833  curenv->add_html_tag(1, ".fl");
834  tok.next();
835}
836
837void macro_diversion::set_diversion_trap(symbol s, vunits n)
838{
839  diversion_trap = s;
840  diversion_trap_pos = n;
841}
842
843void macro_diversion::clear_diversion_trap()
844{
845  diversion_trap = NULL_SYMBOL;
846}
847
848void top_level_diversion::set_diversion_trap(symbol, vunits)
849{
850  error("can't set diversion trap when no current diversion");
851}
852
853void top_level_diversion::clear_diversion_trap()
854{
855  error("can't set diversion trap when no current diversion");
856}
857
858void diversion_trap()
859{
860  vunits n;
861  if (has_arg() && get_vunits(&n, 'v')) {
862    symbol s = get_name();
863    if (!s.is_null())
864      curdiv->set_diversion_trap(s, n);
865    else
866      curdiv->clear_diversion_trap();
867  }
868  else
869    curdiv->clear_diversion_trap();
870  skip_line();
871}
872
873void change_trap()
874{
875  symbol s = get_name(1);
876  if (!s.is_null()) {
877    vunits x;
878    if (has_arg() && get_vunits(&x, 'v'))
879      topdiv->change_trap(s, x);
880    else
881      topdiv->remove_trap(s);
882  }
883  skip_line();
884}
885
886void print_traps()
887{
888  topdiv->print_traps();
889  skip_line();
890}
891
892void mark()
893{
894  symbol s = get_name();
895  if (s.is_null())
896    curdiv->marked_place = curdiv->get_vertical_position();
897  else if (curdiv == topdiv)
898    set_number_reg(s, nl_reg_contents);
899  else
900    set_number_reg(s, curdiv->get_vertical_position().to_units());
901  skip_line();
902}
903
904// This is truly bizarre.  It is documented in the SQ manual.
905
906void return_request()
907{
908  vunits dist = curdiv->marked_place - curdiv->get_vertical_position();
909  if (has_arg()) {
910    if (tok.ch() == '-') {
911      tok.next();
912      vunits x;
913      if (get_vunits(&x, 'v'))
914	dist = -x;
915    }
916    else {
917      vunits x;
918      if (get_vunits(&x, 'v'))
919	dist = x >= V0 ? x - curdiv->get_vertical_position() : V0;
920    }
921  }
922  if (dist < V0)
923    curdiv->space(dist);
924  skip_line();
925}
926
927void vertical_position_traps()
928{
929  int n;
930  if (has_arg() && get_integer(&n))
931    vertical_position_traps_flag = (n != 0);
932  else
933    vertical_position_traps_flag = 1;
934  skip_line();
935}
936
937class page_offset_reg : public reg {
938public:
939  int get_value(units *);
940  const char *get_string();
941};
942
943int page_offset_reg::get_value(units *res)
944{
945  *res = topdiv->get_page_offset().to_units();
946  return 1;
947}
948
949const char *page_offset_reg::get_string()
950{
951  return i_to_a(topdiv->get_page_offset().to_units());
952}
953
954class page_length_reg : public reg {
955public:
956  int get_value(units *);
957  const char *get_string();
958};
959
960int page_length_reg::get_value(units *res)
961{
962  *res = topdiv->get_page_length().to_units();
963  return 1;
964}
965
966const char *page_length_reg::get_string()
967{
968  return i_to_a(topdiv->get_page_length().to_units());
969}
970
971class vertical_position_reg : public reg {
972public:
973  int get_value(units *);
974  const char *get_string();
975};
976
977int vertical_position_reg::get_value(units *res)
978{
979  if (curdiv == topdiv && topdiv->before_first_page)
980    *res = -1;
981  else
982    *res = curdiv->get_vertical_position().to_units();
983  return 1;
984}
985
986const char *vertical_position_reg::get_string()
987{
988  if (curdiv == topdiv && topdiv->before_first_page)
989    return "-1";
990  else
991    return i_to_a(curdiv->get_vertical_position().to_units());
992}
993
994class high_water_mark_reg : public reg {
995public:
996  int get_value(units *);
997  const char *get_string();
998};
999
1000int high_water_mark_reg::get_value(units *res)
1001{
1002  *res = curdiv->get_high_water_mark().to_units();
1003  return 1;
1004}
1005
1006const char *high_water_mark_reg::get_string()
1007{
1008  return i_to_a(curdiv->get_high_water_mark().to_units());
1009}
1010
1011class distance_to_next_trap_reg : public reg {
1012public:
1013  int get_value(units *);
1014  const char *get_string();
1015};
1016
1017int distance_to_next_trap_reg::get_value(units *res)
1018{
1019  *res = curdiv->distance_to_next_trap().to_units();
1020  return 1;
1021}
1022
1023const char *distance_to_next_trap_reg::get_string()
1024{
1025  return i_to_a(curdiv->distance_to_next_trap().to_units());
1026}
1027
1028class diversion_name_reg : public reg {
1029public:
1030  const char *get_string();
1031};
1032
1033const char *diversion_name_reg::get_string()
1034{
1035  return curdiv->get_diversion_name();
1036}
1037
1038class page_number_reg : public general_reg {
1039public:
1040  page_number_reg();
1041  int get_value(units *);
1042  void set_value(units);
1043};
1044
1045page_number_reg::page_number_reg()
1046{
1047}
1048
1049void page_number_reg::set_value(units n)
1050{
1051  topdiv->set_page_number(n);
1052}
1053
1054int page_number_reg::get_value(units *res)
1055{
1056  *res = topdiv->get_page_number();
1057  return 1;
1058}
1059
1060class next_page_number_reg : public reg {
1061public:
1062  const char *get_string();
1063};
1064
1065const char *next_page_number_reg::get_string()
1066{
1067  return i_to_a(topdiv->get_next_page_number());
1068}
1069
1070class page_ejecting_reg : public reg {
1071public:
1072  const char *get_string();
1073};
1074
1075const char *page_ejecting_reg::get_string()
1076{
1077  return i_to_a(topdiv->get_ejecting());
1078}
1079
1080class constant_vunits_reg : public reg {
1081  vunits *p;
1082public:
1083  constant_vunits_reg(vunits *);
1084  const char *get_string();
1085};
1086
1087constant_vunits_reg::constant_vunits_reg(vunits *q) : p(q)
1088{
1089}
1090
1091const char *constant_vunits_reg::get_string()
1092{
1093  return i_to_a(p->to_units());
1094}
1095
1096class nl_reg : public variable_reg {
1097public:
1098  nl_reg();
1099  void set_value(units);
1100};
1101
1102nl_reg::nl_reg() : variable_reg(&nl_reg_contents)
1103{
1104}
1105
1106void nl_reg::set_value(units n)
1107{
1108  variable_reg::set_value(n);
1109  // Setting nl to a negative value when the vertical position in
1110  // the top-level diversion is 0 undoes the top of page transition,
1111  // so that the header macro will be called as if the top of page
1112  // transition hasn't happened.  This is used by Larry Wall's
1113  // wrapman program.  Setting before_first_page to 2 rather than 1,
1114  // tells top_level_diversion::begin_page not to call
1115  // output_file::begin_page again.
1116  if (n < 0 && topdiv->get_vertical_position() == V0)
1117    topdiv->before_first_page = 2;
1118}
1119
1120class no_space_mode_reg : public reg {
1121public:
1122  int get_value(units *);
1123  const char *get_string();
1124};
1125
1126int no_space_mode_reg::get_value(units *val)
1127{
1128  *val = curdiv->no_space_mode;
1129  return 1;
1130}
1131
1132const char *no_space_mode_reg::get_string()
1133{
1134  return curdiv->no_space_mode ? "1" : "0";
1135}
1136
1137void init_div_requests()
1138{
1139  init_request("box", box);
1140  init_request("boxa", box_append);
1141  init_request("bp", begin_page);
1142  init_request("ch", change_trap);
1143  init_request("da", divert_append);
1144  init_request("di", divert);
1145  init_request("dt", diversion_trap);
1146  init_request("fl", flush_output);
1147  init_request("mk", mark);
1148  init_request("ne", need_space);
1149  init_request("ns", no_space);
1150  init_request("os", output_saved_vertical_space);
1151  init_request("pl", page_length);
1152  init_request("pn", page_number);
1153  init_request("po", page_offset);
1154  init_request("ptr", print_traps);
1155  init_request("rs", restore_spacing);
1156  init_request("rt", return_request);
1157  init_request("sp", space_request);
1158  init_request("sv", save_vertical_space);
1159  init_request("vpt", vertical_position_traps);
1160  init_request("wh", when_request);
1161  number_reg_dictionary.define(".a",
1162		       new constant_int_reg(&last_post_line_extra_space));
1163  number_reg_dictionary.define(".d", new vertical_position_reg);
1164  number_reg_dictionary.define(".h", new high_water_mark_reg);
1165  number_reg_dictionary.define(".ne",
1166			       new constant_vunits_reg(&needed_space));
1167  number_reg_dictionary.define(".ns", new no_space_mode_reg);
1168  number_reg_dictionary.define(".o", new page_offset_reg);
1169  number_reg_dictionary.define(".p", new page_length_reg);
1170  number_reg_dictionary.define(".pe", new page_ejecting_reg);
1171  number_reg_dictionary.define(".pn", new next_page_number_reg);
1172  number_reg_dictionary.define(".t", new distance_to_next_trap_reg);
1173  number_reg_dictionary.define(".trunc",
1174			       new constant_vunits_reg(&truncated_space));
1175  number_reg_dictionary.define(".vpt",
1176		       new constant_int_reg(&vertical_position_traps_flag));
1177  number_reg_dictionary.define(".z", new diversion_name_reg);
1178  number_reg_dictionary.define("dl", new variable_reg(&dl_reg_contents));
1179  number_reg_dictionary.define("dn", new variable_reg(&dn_reg_contents));
1180  number_reg_dictionary.define("nl", new nl_reg);
1181  number_reg_dictionary.define("%", new page_number_reg);
1182}
1183