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