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