Line data Source code
1 : /* Copyright 1989-2020 Free Software Foundation, Inc.
2 : 2020-2025 G. Branden Robinson
3 :
4 : Written by James Clark (jjc@jclark.com)
5 :
6 : This file is part of groff, the GNU roff typesetting system.
7 :
8 : groff is free software; you can redistribute it and/or modify it under
9 : the terms of the GNU General Public License as published by the Free
10 : Software Foundation, either version 3 of the License, or
11 : (at your option) any later version.
12 :
13 : groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 : for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 :
21 : // diversions
22 :
23 : #ifdef HAVE_CONFIG_H
24 : #include <config.h>
25 : #endif
26 :
27 : #include <stdlib.h> // exit(), EXIT_SUCCESS
28 :
29 : #include "troff.h"
30 : #include "dictionary.h"
31 : #include "hvunits.h"
32 : #include "stringclass.h"
33 : #include "mtsm.h"
34 : #include "env.h"
35 : #include "request.h"
36 : #include "node.h"
37 : #include "token.h"
38 : #include "div.h"
39 : #include "reg.h"
40 : #include "input.h" // was_invoked_with_regular_control_character
41 :
42 : #include "nonposix.h"
43 :
44 : bool is_exit_underway = false;
45 : bool is_eoi_macro_finished = false;
46 : bool seen_last_page_ejector = false;
47 : static bool began_page_in_eoi_macro = false;
48 : int last_page_number = 0; // if > 0, the number of the last page
49 : // specified with -o
50 :
51 : static int last_post_line_extra_space = 0; // needed for \n(.a
52 : static int nl_reg_contents = -1;
53 : static int dl_reg_contents = 0;
54 : static int dn_reg_contents = 0;
55 : static bool honor_vertical_position_traps = true;
56 : static vunits truncated_space;
57 : static vunits needed_space;
58 :
59 53206 : diversion::diversion(symbol s, bool boxing)
60 : : prev(0 /* nullptr */), nm(s), vertical_position(V0),
61 : high_water_mark(V0), is_box(boxing), is_in_no_space_mode(false),
62 : saved_seen_break(false), saved_seen_space(false),
63 : saved_seen_eol(false), saved_suppress_next_eol(false),
64 53206 : marked_place(V0)
65 : {
66 53206 : }
67 :
68 : struct vertical_size {
69 : vunits pre_extra, post_extra, pre, post;
70 : vertical_size(vunits vs, vunits post_vs);
71 : };
72 :
73 530674 : vertical_size::vertical_size(vunits vs, vunits post_vs)
74 530674 : : pre_extra(V0), post_extra(V0), pre(vs), post(post_vs)
75 : {
76 530674 : }
77 :
78 10996988 : void node::set_vertical_size(vertical_size *)
79 : {
80 10996988 : }
81 :
82 252 : void extra_size_node::set_vertical_size(vertical_size *v)
83 : {
84 252 : if (n < V0) {
85 27 : if (-n > v->pre_extra)
86 27 : v->pre_extra = -n;
87 : }
88 225 : else if (n > v->post_extra)
89 49 : v->post_extra = n;
90 252 : }
91 :
92 3290858 : void vertical_size_node::set_vertical_size(vertical_size *v)
93 : {
94 3290858 : if (n < V0)
95 1645429 : v->pre = -n;
96 : else
97 1645429 : v->post = n;
98 3290858 : }
99 :
100 : top_level_diversion *topdiv;
101 :
102 : diversion *curdiv;
103 :
104 103576 : void do_divert(bool appending, bool boxing)
105 : {
106 103576 : tok.skip_spaces();
107 103576 : symbol nm = read_identifier();
108 103576 : if (nm.is_null()) {
109 51788 : if (curdiv->prev != 0 /* nullptr */) {
110 : // Why the asymmetric diagnostic severity? See Savannah #67139.
111 51788 : if (!(curdiv->is_box) && boxing)
112 1 : fatal("cannot close ordinary diversion with box request");
113 51787 : if (curdiv->is_box && !boxing)
114 0 : error("cannot close box diversion with ordinary diversion"
115 : " request");
116 51787 : curenv->seen_break = curdiv->saved_seen_break;
117 51787 : curenv->seen_space = curdiv->saved_seen_space;
118 51787 : curenv->seen_eol = curdiv->saved_seen_eol;
119 51787 : curenv->suppress_next_eol = curdiv->saved_suppress_next_eol;
120 51787 : if (boxing) {
121 2809 : curenv->line = curdiv->saved_line;
122 2809 : curenv->width_total = curdiv->saved_width_total;
123 2809 : curenv->space_total = curdiv->saved_space_total;
124 2809 : curenv->saved_indent = curdiv->saved_saved_indent;
125 2809 : curenv->target_text_length = curdiv->saved_target_text_length;
126 2809 : curenv->was_previous_line_interrupted
127 2809 : = curdiv->saved_was_previous_line_interrupted;
128 : }
129 51787 : diversion *temp = curdiv;
130 51787 : curdiv = curdiv->prev;
131 51787 : delete temp;
132 : }
133 : else
134 0 : warning(WARN_DI, "diversion stack underflow");
135 : }
136 : else {
137 51788 : macro_diversion *md = new macro_diversion(nm, appending, boxing);
138 51788 : md->prev = curdiv;
139 51788 : curdiv = md;
140 51788 : curdiv->saved_seen_break = curenv->seen_break;
141 51788 : curdiv->saved_seen_space = curenv->seen_space;
142 51788 : curdiv->saved_seen_eol = curenv->seen_eol;
143 51788 : curdiv->saved_suppress_next_eol = curenv->suppress_next_eol;
144 51788 : curenv->seen_break = false;
145 51788 : curenv->seen_space = false;
146 51788 : curenv->seen_eol = false;
147 51788 : if (boxing) {
148 2809 : curdiv->saved_line = curenv->line;
149 2809 : curdiv->saved_width_total = curenv->width_total;
150 2809 : curdiv->saved_space_total = curenv->space_total;
151 2809 : curdiv->saved_saved_indent = curenv->saved_indent;
152 2809 : curdiv->saved_target_text_length = curenv->target_text_length;
153 2809 : curdiv->saved_was_previous_line_interrupted
154 2809 : = curenv->was_previous_line_interrupted;
155 2809 : curenv->line = 0 /* nullptr */;
156 2809 : curenv->start_line();
157 : }
158 : }
159 103575 : skip_line();
160 103575 : }
161 :
162 97279 : static void divert() // .di
163 : {
164 97279 : do_divert(false /* appending */, false /* boxing */);
165 97279 : }
166 :
167 678 : static void divert_append() // .da
168 : {
169 678 : do_divert(true /* appending */, false /* boxing */);
170 678 : }
171 :
172 5555 : static void box() // .box
173 : {
174 5555 : do_divert(false /* appending */, true /* boxing */);
175 5554 : }
176 :
177 64 : static void box_append() // .boxa
178 : {
179 64 : do_divert(true /* appending */, true /* boxing */);
180 64 : }
181 :
182 19978 : void diversion::need(vunits n)
183 : {
184 19978 : vunits d = distance_to_next_trap();
185 19978 : if (d < n) {
186 451 : truncated_space = -d;
187 451 : needed_space = n;
188 451 : space(d, 1);
189 : }
190 19978 : }
191 :
192 51788 : macro_diversion::macro_diversion(symbol s, bool appending, bool boxing)
193 : : diversion(s, boxing), max_width(H0), diversion_trap(0 /* nullptr */),
194 51788 : diversion_trap_pos(0)
195 : {
196 : #if 0
197 : if (append) {
198 : /* We don't allow recursive appends, e.g.:
199 :
200 : .da a
201 : .a
202 : .di
203 :
204 : This causes an infinite loop in troff anyway.
205 : This is because the user could do
206 :
207 : .as a foo
208 :
209 : in the diversion, and this would mess things up royally,
210 : since there would be two things appending to the same
211 : macro_header.
212 : To make it work, we would have to copy the _contents_
213 : of the macro into which we were diverting; this doesn't
214 : strike me as worthwhile.
215 : However,
216 :
217 : .di a
218 : .a
219 : .a
220 : .di
221 :
222 : will work and will make 'a' contain two copies of what it
223 : contained before; in troff, 'a' would contain nothing. */
224 : request_or_macro *rm
225 : = static_cast<request_or_macro *>(request_dictionary.remove(s));
226 : if (!rm || (0 /* nullptr */ == (mac = rm->to_macro()))
227 : mac = new macro;
228 : }
229 : else
230 : mac = new macro;
231 : #endif
232 : // We can now catch the situation described above by comparing
233 : // the length of the charlist in the macro_header with the length
234 : // stored in the macro. When we detect this, we copy the contents.
235 51788 : mac = new macro(true /* is diversion */);
236 51788 : if (appending) {
237 : request_or_macro *rm
238 419 : = static_cast<request_or_macro *>(request_dictionary.lookup(s));
239 419 : if (rm) {
240 321 : macro *m = rm->to_macro();
241 321 : if (m)
242 321 : *mac = *m;
243 : }
244 : }
245 51788 : }
246 :
247 207148 : macro_diversion::~macro_diversion()
248 : {
249 : request_or_macro *rm
250 51787 : = static_cast<request_or_macro *>(request_dictionary.lookup(nm));
251 51787 : macro *m = rm ? rm->to_macro() : 0 /* nullptr */;
252 51787 : if (m) {
253 4927 : *m = *mac;
254 4927 : delete mac;
255 : }
256 : else
257 46860 : request_dictionary.define(nm, mac);
258 51787 : mac = 0 /* nullptr */;
259 51787 : dl_reg_contents = max_width.to_units();
260 51787 : dn_reg_contents = vertical_position.to_units();
261 103574 : }
262 :
263 : static const int DIVERSION_LENGTH_MAX = INT_MAX;
264 :
265 668 : vunits macro_diversion::distance_to_next_trap()
266 : {
267 668 : vunits distance = 0;
268 668 : if (!diversion_trap.is_null()
269 668 : && (diversion_trap_pos > vertical_position))
270 0 : distance = diversion_trap_pos - vertical_position;
271 : else
272 : // Do the (saturating) arithmetic ourselves to avoid an error
273 : // diagnostic from constructor in number.cpp.
274 668 : distance = units(DIVERSION_LENGTH_MAX / vresolution);
275 668 : assert(distance >= 0);
276 668 : return distance;
277 : }
278 :
279 2 : const char *macro_diversion::get_next_trap_name()
280 : {
281 2 : if (!diversion_trap.is_null()
282 2 : && (diversion_trap_pos > vertical_position))
283 1 : return diversion_trap.contents();
284 : else
285 1 : return "";
286 : }
287 :
288 233376 : void macro_diversion::transparent_output(unsigned char c)
289 : {
290 233376 : mac->append(c);
291 233376 : }
292 :
293 0 : void macro_diversion::transparent_output(node *n)
294 : {
295 0 : mac->append(n);
296 0 : }
297 :
298 315916 : void macro_diversion::output(node *nd, bool retain_size,
299 : vunits vs, vunits post_vs, hunits width)
300 : {
301 315916 : is_in_no_space_mode = false;
302 315916 : vertical_size v(vs, post_vs);
303 8104840 : while (nd != 0 /* nullptr */) {
304 7788924 : nd->set_vertical_size(&v);
305 7788924 : node *temp = nd;
306 7788924 : nd = nd->next;
307 7788924 : if (temp->interpret(mac))
308 430 : delete temp;
309 : else {
310 : #if 1
311 7788494 : temp->freeze_space();
312 : #endif
313 7788494 : mac->append(temp);
314 : }
315 : }
316 315916 : last_post_line_extra_space = v.post_extra.to_units();
317 315916 : if (!retain_size) {
318 37277 : v.pre = vs;
319 37277 : v.post = post_vs;
320 : }
321 315916 : if (width > max_width)
322 64065 : max_width = width;
323 315916 : vunits x = v.pre + v.pre_extra + v.post + v.post_extra;
324 315916 : int new_vpos = 0;
325 315916 : int vpos = vertical_position.to_units();
326 315916 : int lineht = x.to_units();
327 315916 : bool overflow = false;
328 315916 : if (ckd_add(&new_vpos, vpos, lineht))
329 0 : overflow = true;
330 : else if (new_vpos > DIVERSION_LENGTH_MAX)
331 : overflow = true;
332 315916 : if (overflow)
333 0 : fatal("diversion overflow (vertical position: %1u,"
334 0 : " next line height: %2u)", vpos, lineht);
335 315916 : if (honor_vertical_position_traps
336 310717 : && !diversion_trap.is_null()
337 1 : && (diversion_trap_pos > vertical_position)
338 626633 : && (diversion_trap_pos <= vertical_position + x)) {
339 1 : vunits trunc = vertical_position + x - diversion_trap_pos;
340 1 : if (trunc > v.post)
341 0 : trunc = v.post;
342 1 : v.post -= trunc;
343 1 : x -= trunc;
344 1 : truncated_space = trunc;
345 1 : spring_trap(diversion_trap);
346 : }
347 315916 : mac->append(new vertical_size_node(-v.pre));
348 315916 : mac->append(new vertical_size_node(v.post));
349 315916 : mac->append('\n');
350 315916 : vertical_position += x;
351 315916 : if (vertical_position - v.post > high_water_mark)
352 72372 : high_water_mark = vertical_position - v.post;
353 315916 : }
354 :
355 526275 : void macro_diversion::space(vunits n, bool /* forcing */)
356 : {
357 526275 : if (honor_vertical_position_traps
358 521970 : && !diversion_trap.is_null()
359 0 : && diversion_trap_pos > vertical_position
360 1048245 : && diversion_trap_pos <= vertical_position + n) {
361 0 : truncated_space = vertical_position + n - diversion_trap_pos;
362 0 : n = diversion_trap_pos - vertical_position;
363 0 : spring_trap(diversion_trap);
364 : }
365 526275 : else if (n + vertical_position < V0)
366 51 : n = -vertical_position;
367 526275 : mac->append(new diverted_space_node(n));
368 526275 : vertical_position += n;
369 526275 : }
370 :
371 2 : void macro_diversion::copy_file(const char *filename)
372 : {
373 2 : mac->append(new diverted_copy_file_node(filename));
374 2 : }
375 :
376 1418 : top_level_diversion::top_level_diversion()
377 : : page_number(0), page_count(0), last_page_count(-1),
378 : page_length(units_per_inch*11),
379 : prev_page_offset(units_per_inch), page_offset(units_per_inch),
380 : page_trap_list(0 /* nullptr */), overriding_next_page_number(false),
381 1418 : ejecting_page(false), before_first_page_status(1)
382 : {
383 1418 : }
384 :
385 : // find the next trap after pos
386 :
387 524538 : trap *top_level_diversion::find_next_trap(vunits *next_trap_pos)
388 : {
389 524538 : trap *next_trap = 0 /* nullptr */;
390 1636852 : for (trap *pt = page_trap_list; pt != 0 /* nullptr */; pt = pt->next)
391 1112314 : if (!pt->nm.is_null()) {
392 1083041 : if (pt->position >= V0) {
393 473464 : if ((pt->position > vertical_position)
394 3736 : && (pt->position < page_length)
395 477655 : && ((0 /* nullptr */ == next_trap)
396 455 : || pt->position < *next_trap_pos)) {
397 3677 : next_trap = pt;
398 3677 : *next_trap_pos = pt->position;
399 : }
400 : }
401 : else {
402 609577 : vunits pos = pt->position;
403 609577 : pos += page_length;
404 609577 : if ((pos > 0)
405 609575 : && (pos > vertical_position)
406 1363043 : && ((0 /* nullptr */ == next_trap)
407 143891 : || (pos < *next_trap_pos))) {
408 551765 : next_trap = pt;
409 551765 : *next_trap_pos = pos;
410 : }
411 : }
412 : }
413 524538 : return next_trap;
414 : }
415 :
416 55244 : vunits top_level_diversion::distance_to_next_trap()
417 : {
418 55244 : vunits d;
419 55244 : if (!find_next_trap(&d))
420 16072 : return page_length - vertical_position;
421 : else
422 39172 : return d - vertical_position;
423 : }
424 :
425 2 : const char *top_level_diversion::get_next_trap_name()
426 : {
427 2 : vunits next_trap_pos;
428 2 : trap *next_trap = find_next_trap(&next_trap_pos);
429 2 : if (0 /* nullptr */ == next_trap)
430 1 : return "";
431 : else
432 1 : return next_trap->nm.contents();
433 : }
434 :
435 : // This is used by more than just top-level diversions.
436 214758 : void top_level_diversion::output(node *nd, bool retain_size,
437 : vunits vs, vunits post_vs,
438 : hunits width)
439 : {
440 214758 : is_in_no_space_mode = false;
441 214758 : vunits next_trap_pos;
442 214758 : trap *next_trap = find_next_trap(&next_trap_pos);
443 214758 : if ((before_first_page_status > 0) && begin_page())
444 0 : fatal("attempting diversion output before first page has started,"
445 : " when a top-of-page trap is defined; invoke break or flush"
446 : " request beforehand");
447 214758 : vertical_size v(vs, post_vs);
448 6713932 : for (node *tem = nd; tem != 0 /* nullptr */; tem = tem->next)
449 6499174 : tem->set_vertical_size(&v);
450 214758 : last_post_line_extra_space = v.post_extra.to_units();
451 214758 : if (!retain_size) {
452 62467 : v.pre = vs;
453 62467 : v.post = post_vs;
454 : }
455 214758 : vertical_position += v.pre;
456 214758 : vertical_position += v.pre_extra;
457 214758 : the_output->print_line(page_offset, vertical_position, nd,
458 214758 : v.pre + v.pre_extra, v.post_extra, width);
459 214758 : vertical_position += v.post_extra;
460 214758 : if (vertical_position > high_water_mark)
461 85399 : high_water_mark = vertical_position;
462 214758 : if (honor_vertical_position_traps && vertical_position >= page_length)
463 30 : begin_page();
464 214728 : else if (honor_vertical_position_traps
465 205473 : && (next_trap != 0 /* nullptr */)
466 420201 : && (vertical_position >= next_trap_pos)) {
467 554 : nl_reg_contents = vertical_position.to_units();
468 554 : truncated_space = v.post;
469 554 : spring_trap(next_trap->nm);
470 : }
471 214174 : else if (v.post > V0) {
472 2 : vertical_position += v.post;
473 2 : if (honor_vertical_position_traps
474 2 : && (next_trap != 0 /* nullptr */)
475 4 : && (vertical_position >= next_trap_pos)) {
476 0 : truncated_space = vertical_position - next_trap_pos;
477 0 : vertical_position = next_trap_pos;
478 0 : nl_reg_contents = vertical_position.to_units();
479 0 : spring_trap(next_trap->nm);
480 : }
481 2 : else if (honor_vertical_position_traps
482 2 : && (vertical_position >= page_length))
483 0 : begin_page();
484 : else
485 2 : nl_reg_contents = vertical_position.to_units();
486 : }
487 : else
488 214172 : nl_reg_contents = vertical_position.to_units();
489 214743 : }
490 :
491 : // The next two member functions implement the internals of `.output`
492 : // and `\!`.
493 :
494 356631 : void top_level_diversion::transparent_output(unsigned char c)
495 : {
496 356631 : if ((before_first_page_status > 0) && begin_page())
497 0 : fatal("attempting transparent output from top-level diversion"
498 : " before first page has started, when a top-of-page trap is"
499 : " defined; invoke break or flush request beforehand");
500 356631 : const char *s = encode_for_stream_output(c);
501 713426 : while (*s)
502 356795 : the_output->transparent_char(*s++);
503 356631 : }
504 :
505 0 : void top_level_diversion::transparent_output(node * /*n*/)
506 : {
507 : // TODO: When Savannah #63074 is fixed, the user will have a way to
508 : // avoid this error.
509 0 : error("cannot write a node to device-independent output");
510 0 : }
511 :
512 : // Implement the internals of `.cf`.
513 2 : void top_level_diversion::copy_file(const char *filename)
514 : {
515 2 : if ((before_first_page_status > 0) && begin_page())
516 0 : fatal("attempting transparent copy of file to top-level diversion"
517 : " before first page has started, when a top-of-page trap is"
518 : " defined; invoke break or flush request beforehand");
519 2 : the_output->copy_file(page_offset, vertical_position, filename);
520 2 : }
521 :
522 251997 : void top_level_diversion::space(vunits n, bool forcing)
523 : {
524 251997 : if (is_in_no_space_mode) {
525 374 : if (!forcing)
526 118 : return;
527 : else
528 257 : is_in_no_space_mode = false;
529 : }
530 251880 : if (before_first_page_status > 0) {
531 1 : begin_page(n);
532 1 : return;
533 : }
534 251879 : vunits next_trap_pos;
535 251879 : trap *next_trap = find_next_trap(&next_trap_pos);
536 251879 : vunits y = vertical_position + n;
537 251879 : if (curenv->get_vertical_spacing().to_units())
538 251777 : curenv->seen_space += n.to_units()
539 251777 : / curenv->get_vertical_spacing().to_units();
540 251879 : if (honor_vertical_position_traps
541 242744 : && (next_trap != 0 /* nullptr */)
542 494623 : && (y >= next_trap_pos)) {
543 2264 : vertical_position = next_trap_pos;
544 2264 : nl_reg_contents = vertical_position.to_units();
545 2264 : truncated_space = y - vertical_position;
546 2264 : spring_trap(next_trap->nm);
547 : }
548 249615 : else if (y < V0) {
549 21 : vertical_position = V0;
550 21 : nl_reg_contents = vertical_position.to_units();
551 : }
552 249594 : else if (honor_vertical_position_traps
553 249594 : && (y >= page_length && n >= V0))
554 2621 : begin_page(y - page_length);
555 : else {
556 246973 : vertical_position = y;
557 246973 : nl_reg_contents = vertical_position.to_units();
558 : }
559 : }
560 :
561 612 : trap::trap(symbol s, vunits n, trap *p)
562 612 : : next(p), position(n), nm(s)
563 : {
564 612 : }
565 :
566 2093 : void top_level_diversion::add_trap(symbol nam, vunits pos)
567 : {
568 2093 : trap *first_free_slot = 0 /* nullptr*/;
569 : trap **p;
570 6747 : for (p = &page_trap_list; *p; p = &(*p)->next) {
571 5168 : if ((*p)->nm.is_null()) {
572 1856 : if (0 /* nullptr*/ == first_free_slot)
573 967 : first_free_slot = *p;
574 : }
575 3312 : else if ((*p)->position == pos) {
576 514 : (*p)->nm = nam;
577 514 : return;
578 : }
579 : }
580 1579 : if (first_free_slot) {
581 967 : first_free_slot->nm = nam;
582 967 : first_free_slot->position = pos;
583 : }
584 : else
585 612 : *p = new trap(nam, pos, 0 /* nullptr*/);
586 : }
587 :
588 2396 : void top_level_diversion::remove_trap(symbol nam)
589 : {
590 7459 : for (trap *p = page_trap_list; p; p = p->next)
591 6204 : if (p->nm == nam) {
592 1141 : p->nm = NULL_SYMBOL;
593 1141 : return;
594 : }
595 : }
596 :
597 11 : void top_level_diversion::remove_trap_at(vunits pos)
598 : {
599 11 : for (trap *p = page_trap_list; p; p = p->next)
600 11 : if (p->position == pos) {
601 11 : p->nm = NULL_SYMBOL;
602 11 : return;
603 : }
604 : }
605 :
606 811 : void top_level_diversion::change_trap(symbol nam, vunits pos)
607 : {
608 1873 : for (trap *p = page_trap_list; p; p = p->next)
609 1867 : if (p->nm == nam) {
610 805 : p->position = pos;
611 805 : return;
612 : }
613 6 : warning(WARN_MAC, "cannot move unplanted trap macro '%1'",
614 12 : nam.contents());
615 : }
616 :
617 1 : void top_level_diversion::print_traps()
618 : {
619 2 : for (trap *p = page_trap_list; p; p = p->next)
620 1 : if (p->nm.is_null())
621 0 : fprintf(stderr, " empty\n");
622 : else
623 1 : fprintf(stderr, "%s\t%d\n", p->nm.contents(),
624 : p->position.to_units());
625 1 : fflush(stderr);
626 1 : }
627 :
628 1184 : void end_diversions()
629 : {
630 1184 : while (curdiv != topdiv) {
631 0 : error("automatically ending diversion '%1' on exit",
632 0 : curdiv->nm.contents());
633 0 : diversion *tem = curdiv;
634 0 : curdiv = curdiv->prev;
635 0 : delete tem;
636 : }
637 1184 : }
638 :
639 : // input.cpp:exit_troff() cleans up most formatter state.
640 1419 : void write_any_trailer_and_exit(int exit_code)
641 : {
642 : // If output was never initialized, there is no trailer to write.
643 1419 : if (the_output != 0 /* nullptr */) {
644 1280 : the_output->trailer(topdiv->get_page_length());
645 : // If we're already dying, don't call the_output's destructor. See
646 : // node.cpp:real_output_file::~real_output_file().
647 1280 : if (!the_output->is_dying)
648 1279 : delete the_output;
649 : }
650 : FLUSH_INPUT_PIPE(STDIN_FILENO);
651 1418 : exit(exit_code);
652 : }
653 :
654 : // Returns `true` if beginning the page sprung a top-of-page trap.
655 : // The optional parameter is for the .trunc register.
656 4058 : bool top_level_diversion::begin_page(vunits n)
657 : {
658 4058 : if (is_exit_underway) {
659 2810 : if (page_count == last_page_count
660 1407 : ? curenv->is_empty()
661 3 : : (is_eoi_macro_finished && (seen_last_page_ejector
662 1 : || began_page_in_eoi_macro)))
663 1403 : write_any_trailer_and_exit(EXIT_SUCCESS);
664 2 : if (!is_eoi_macro_finished)
665 2 : began_page_in_eoi_macro = true;
666 : }
667 2655 : if (last_page_number > 0 && page_number == last_page_number)
668 0 : write_any_trailer_and_exit(EXIT_SUCCESS);
669 2655 : if (0 /* nullptr */ == the_output)
670 1234 : init_output();
671 2655 : ++page_count;
672 2655 : if (overriding_next_page_number) {
673 160 : page_number = next_page_number;
674 160 : overriding_next_page_number = false;
675 : }
676 2495 : else if (before_first_page_status == 1)
677 1275 : page_number = 1;
678 : else
679 1220 : page_number++;
680 : // spring the top of page trap if there is one
681 2655 : vunits next_trap_pos;
682 2655 : vertical_position = -vresolution;
683 2655 : trap *next_trap = find_next_trap(&next_trap_pos);
684 2655 : vertical_position = V0;
685 2655 : high_water_mark = V0;
686 2655 : ejecting_page = false;
687 : // If before_first_page_status was 2, then the top of page transition
688 : // was undone using ".nr nl 0-1" or similar. See nl_reg::set_value.
689 2655 : if (before_first_page_status != 2)
690 2655 : the_output->begin_page(page_number, page_length);
691 2655 : before_first_page_status = 0;
692 2655 : nl_reg_contents = vertical_position.to_units();
693 2655 : if ((honor_vertical_position_traps && (next_trap != 0 /* nullptr */))
694 5310 : && (next_trap_pos == V0)) {
695 1301 : truncated_space = n;
696 1301 : spring_trap(next_trap->nm);
697 1301 : return true;
698 : }
699 : else
700 1354 : return false;
701 : }
702 :
703 4856 : void continue_page_eject()
704 : {
705 4856 : if (topdiv->get_ejecting()) {
706 3052 : if (curdiv != topdiv)
707 0 : error("cannot continue page ejection while diverting output");
708 3052 : else if (!honor_vertical_position_traps)
709 0 : error("cannot continue page ejection while vertical position"
710 : " traps disabled");
711 : else {
712 3052 : push_page_ejector();
713 3052 : topdiv->space(topdiv->get_page_length(), true /* forcing */);
714 : }
715 : }
716 4659 : }
717 :
718 212 : void top_level_diversion::set_next_page_number(int n)
719 : {
720 212 : next_page_number = n;
721 212 : overriding_next_page_number = true;
722 212 : }
723 :
724 1 : int top_level_diversion::get_next_page_number()
725 : {
726 1 : return overriding_next_page_number ? next_page_number
727 1 : : (page_number + 1);
728 : }
729 :
730 934 : void top_level_diversion::set_page_length(vunits n)
731 : {
732 934 : page_length = n;
733 934 : }
734 :
735 51787 : diversion::~diversion()
736 : {
737 51787 : }
738 :
739 5078 : void configure_page_offset_request()
740 : {
741 5078 : hunits n;
742 : // The troff manual says that the default scaling indicator is v,
743 : // but it is in fact m: v wouldn't make sense for a horizontally
744 : // oriented request.
745 5078 : if (!has_arg() || !read_hunits(&n, 'm', topdiv->get_page_offset()))
746 42 : n = topdiv->get_previous_page_offset();
747 5078 : topdiv->set_page_offset(n);
748 5078 : topdiv->modified_tag.incl(MTSM_PO);
749 5078 : skip_line();
750 5078 : }
751 :
752 934 : static void configure_page_length_request() // .pl
753 : {
754 934 : vunits temp;
755 934 : if (has_arg() && read_vunits(&temp, 'v', topdiv->get_page_length())) {
756 932 : if (temp < vresolution) {
757 4 : warning(WARN_RANGE, "setting computed page length %1u to device"
758 : " vertical motion quantum",
759 4 : temp.to_units());
760 4 : temp = vresolution;
761 : }
762 932 : topdiv->set_page_length(temp);
763 : }
764 : else
765 2 : topdiv->set_page_length(11 * units_per_inch);
766 934 : skip_line();
767 934 : }
768 :
769 2104 : static void when_request() // .wh
770 : {
771 2104 : vunits n;
772 2104 : if (read_vunits(&n, 'v')) {
773 2104 : symbol s = read_identifier();
774 2104 : if (s.is_null())
775 11 : topdiv->remove_trap_at(n);
776 : else
777 2093 : topdiv->add_trap(s, n);
778 : }
779 2104 : skip_line();
780 2104 : }
781 :
782 2030 : static void begin_page() // .bp
783 : {
784 2030 : bool got_arg = false;
785 2030 : int n = 0;
786 2030 : if (has_arg() && read_integer(&n, topdiv->get_page_number()))
787 204 : got_arg = true;
788 2168 : while (!tok.is_newline() && !tok.is_eof())
789 138 : tok.next();
790 2030 : if (curdiv == topdiv) {
791 2030 : if (topdiv->before_first_page_status > 0) {
792 3 : if (!was_invoked_with_regular_control_character) {
793 0 : if (got_arg)
794 0 : topdiv->set_next_page_number(n);
795 0 : if (got_arg || !topdiv->is_in_no_space_mode)
796 0 : topdiv->begin_page();
797 : }
798 3 : else if (topdiv->is_in_no_space_mode && !got_arg)
799 0 : topdiv->begin_page();
800 : else {
801 : /* Given this
802 :
803 : .wh 0 x
804 : .de x
805 : .tm \\n%
806 : ..
807 : .bp 3
808 :
809 : troff prints
810 :
811 : 1
812 : 3
813 :
814 : This code makes groff do the same. */
815 :
816 3 : push_page_ejector();
817 3 : topdiv->begin_page();
818 3 : if (got_arg)
819 1 : topdiv->set_next_page_number(n);
820 3 : topdiv->set_ejecting();
821 : }
822 : }
823 : else {
824 2027 : push_page_ejector();
825 2027 : if (was_invoked_with_regular_control_character)
826 708 : curenv->do_break();
827 2027 : if (got_arg)
828 203 : topdiv->set_next_page_number(n);
829 2027 : if (!(topdiv->is_in_no_space_mode && !got_arg))
830 2008 : topdiv->set_ejecting();
831 : }
832 : }
833 2030 : tok.next();
834 2030 : }
835 :
836 24848 : static void no_space() // .ns
837 : {
838 24848 : curdiv->is_in_no_space_mode = true;
839 24848 : skip_line();
840 24848 : }
841 :
842 1091 : static void restore_spacing() // .re
843 : {
844 1091 : curdiv->is_in_no_space_mode = false;
845 1091 : skip_line();
846 1091 : }
847 :
848 : /* It is necessary to generate a break before reading the argument,
849 : because otherwise arguments using | will be wrong. But if we just
850 : generate a break as usual, then the line forced out may spring a trap
851 : and thus push a macro onto the input stack before we have had a chance
852 : to read the argument to the sp request. We resolve this dilemma by
853 : setting, before generating the break, a flag which will postpone the
854 : actual pushing of the macro associated with the trap sprung by the
855 : outputting of the line forced out by the break till after we have read
856 : the argument to the request. If the break did cause a trap to be
857 : sprung, then we don't actually do the space. */
858 :
859 249530 : static void space_request() // .sp
860 : {
861 249530 : postpone_traps();
862 249530 : if (was_invoked_with_regular_control_character)
863 249118 : curenv->do_break();
864 249530 : vunits n;
865 249530 : if (!has_arg() || !read_vunits(&n, 'v'))
866 450 : n = curenv->get_vertical_spacing();
867 349185 : while (!tok.is_newline() && !tok.is_eof())
868 99655 : tok.next();
869 249530 : if (!unpostpone_traps() && !curdiv->is_in_no_space_mode)
870 246651 : curdiv->space(n);
871 : else
872 : // The line might have had line spacing that was truncated.
873 2879 : truncated_space += n;
874 :
875 249530 : tok.next();
876 249530 : }
877 :
878 80 : void blank_line()
879 : {
880 80 : curenv->do_break();
881 80 : if (!was_trap_sprung && !curdiv->is_in_no_space_mode)
882 80 : curdiv->space(curenv->get_vertical_spacing());
883 : else
884 0 : truncated_space += curenv->get_vertical_spacing();
885 80 : }
886 :
887 : /* need_space might spring a trap and so we must be careful that the
888 : BEGIN_TRAP token is not skipped over. */
889 :
890 19978 : static void need_space() // .ne
891 : {
892 19978 : vunits n;
893 19978 : if (!has_arg() || !read_vunits(&n, 'v'))
894 0 : n = curenv->get_vertical_spacing();
895 20394 : while (!tok.is_newline() && !tok.is_eof())
896 416 : tok.next();
897 19978 : curdiv->need(n);
898 19978 : tok.next();
899 19978 : }
900 :
901 8 : static void page_number() // .pn
902 : {
903 8 : if (!has_arg()) {
904 0 : warning(WARN_MISSING, "page number assignment request expects an"
905 : " argument");
906 0 : skip_line();
907 0 : return;
908 : }
909 8 : int n = 0;
910 : // the ps4html register is set if we are using -Tps
911 : // to generate images for html
912 : // XXX: Yuck! Get rid of this; macro packages already test the
913 : // register before invoking .pn.
914 8 : reg *r = static_cast<reg *>(register_dictionary.lookup("ps4html"));
915 8 : if (0 /* nullptr */ == r)
916 8 : if (has_arg() && read_integer(&n, topdiv->get_page_number()))
917 8 : topdiv->set_next_page_number(n);
918 8 : skip_line();
919 : }
920 :
921 : vunits saved_space;
922 :
923 0 : static void save_vertical_space() // .sv
924 : {
925 0 : vunits x;
926 0 : if (!has_arg() || !read_vunits(&x, 'v'))
927 0 : x = curenv->get_vertical_spacing();
928 0 : if (curdiv->distance_to_next_trap() > x)
929 0 : curdiv->space(x, true /* forcing */);
930 : else
931 0 : saved_space = x;
932 0 : skip_line();
933 0 : }
934 :
935 85 : static void output_saved_vertical_space() // .os
936 : {
937 85 : while (!tok.is_newline() && !tok.is_eof())
938 0 : tok.next();
939 85 : if (saved_space > V0)
940 0 : curdiv->space(saved_space, true /* forcing */);
941 85 : saved_space = V0;
942 85 : tok.next();
943 85 : }
944 :
945 15 : static void flush_request() // .fl
946 : {
947 15 : while (!tok.is_newline() && !tok.is_eof())
948 0 : tok.next();
949 15 : if (was_invoked_with_regular_control_character)
950 15 : curenv->do_break();
951 15 : if (the_output != 0 /* nullptr */)
952 15 : the_output->flush();
953 15 : tok.next();
954 15 : }
955 :
956 1 : void macro_diversion::set_diversion_trap(symbol s, vunits n)
957 : {
958 1 : diversion_trap = s;
959 1 : diversion_trap_pos = n;
960 1 : }
961 :
962 0 : void macro_diversion::clear_diversion_trap()
963 : {
964 0 : diversion_trap = NULL_SYMBOL;
965 0 : }
966 :
967 0 : void top_level_diversion::set_diversion_trap(symbol, vunits)
968 : {
969 0 : error("cannot set diversion trap when not diverting output");
970 0 : }
971 :
972 0 : void top_level_diversion::clear_diversion_trap()
973 : {
974 0 : error("cannot clear diversion trap when not diverting output");
975 0 : }
976 :
977 1 : static void diversion_trap() // .dt
978 : {
979 1 : vunits n;
980 1 : if (has_arg() && read_vunits(&n, 'v')) {
981 1 : symbol s = read_identifier();
982 1 : if (!s.is_null())
983 1 : curdiv->set_diversion_trap(s, n);
984 : else
985 0 : curdiv->clear_diversion_trap();
986 : }
987 : else
988 0 : curdiv->clear_diversion_trap();
989 1 : skip_line();
990 1 : }
991 :
992 3207 : static void change_trap() // .ch
993 : {
994 3207 : symbol s = read_identifier(true /* required */);
995 3207 : if (!s.is_null()) {
996 3207 : vunits x;
997 3207 : if (has_arg() && read_vunits(&x, 'v'))
998 811 : topdiv->change_trap(s, x);
999 : else
1000 2396 : topdiv->remove_trap(s);
1001 : }
1002 3207 : skip_line();
1003 3207 : }
1004 :
1005 1 : static void print_traps() // .pwh
1006 : {
1007 1 : topdiv->print_traps();
1008 1 : skip_line();
1009 1 : }
1010 :
1011 21791 : static void mark() // .mk
1012 : {
1013 21791 : symbol s = read_identifier();
1014 21791 : if (s.is_null())
1015 852 : curdiv->marked_place = curdiv->get_vertical_position();
1016 20939 : else if (curdiv == topdiv)
1017 9457 : set_register(s, nl_reg_contents);
1018 : else
1019 11482 : set_register(s, curdiv->get_vertical_position().to_units());
1020 21791 : skip_line();
1021 21791 : }
1022 :
1023 : // This is truly bizarre. It is documented in the SQ manual.
1024 :
1025 1083 : static void return_request() // .rt
1026 : {
1027 1083 : vunits dist = curdiv->marked_place - curdiv->get_vertical_position();
1028 1083 : if (has_arg()) {
1029 225 : if (tok.ch() == int('-')) { // TODO: grochar
1030 0 : tok.next();
1031 0 : vunits x;
1032 0 : if (read_vunits(&x, 'v'))
1033 0 : dist = -x;
1034 : }
1035 : else {
1036 225 : vunits x;
1037 225 : if (read_vunits(&x, 'v'))
1038 225 : dist = x >= V0 ? x - curdiv->get_vertical_position() : V0;
1039 : }
1040 : }
1041 1083 : if (dist < V0)
1042 579 : curdiv->space(dist);
1043 1083 : skip_line();
1044 1083 : }
1045 :
1046 22753 : static void vertical_position_traps() // .vpt
1047 : {
1048 22753 : int n = 0;
1049 22753 : if (has_arg() && read_integer(&n))
1050 21526 : honor_vertical_position_traps = (n > 0);
1051 : else
1052 1227 : honor_vertical_position_traps = true;
1053 22753 : skip_line();
1054 22753 : }
1055 :
1056 : class page_offset_reg : public reg {
1057 : public:
1058 : bool get_value(units *);
1059 : const char *get_string();
1060 : };
1061 :
1062 59 : bool page_offset_reg::get_value(units *res)
1063 : {
1064 59 : *res = topdiv->get_page_offset().to_units();
1065 59 : return true;
1066 : }
1067 :
1068 363 : const char *page_offset_reg::get_string()
1069 : {
1070 363 : return i_to_a(topdiv->get_page_offset().to_units());
1071 : }
1072 :
1073 : class page_length_reg : public reg {
1074 : public:
1075 : bool get_value(units *);
1076 : const char *get_string();
1077 : };
1078 :
1079 0 : bool page_length_reg::get_value(units *res)
1080 : {
1081 0 : *res = topdiv->get_page_length().to_units();
1082 0 : return true;
1083 : }
1084 :
1085 1612 : const char *page_length_reg::get_string()
1086 : {
1087 1612 : return i_to_a(topdiv->get_page_length().to_units());
1088 : }
1089 :
1090 : class vertical_position_reg : public reg {
1091 : public:
1092 : bool get_value(units *);
1093 : const char *get_string();
1094 : };
1095 :
1096 0 : bool vertical_position_reg::get_value(units *res)
1097 : {
1098 0 : if ((curdiv == topdiv) && (topdiv->before_first_page_status > 0))
1099 0 : *res = -1;
1100 : else
1101 0 : *res = curdiv->get_vertical_position().to_units();
1102 0 : return true;
1103 : }
1104 :
1105 7329 : const char *vertical_position_reg::get_string()
1106 : {
1107 7329 : if ((curdiv == topdiv) && (topdiv->before_first_page_status > 0))
1108 0 : return "-1";
1109 : else
1110 7329 : return i_to_a(curdiv->get_vertical_position().to_units());
1111 : }
1112 :
1113 : class high_water_mark_reg : public reg {
1114 : public:
1115 : bool get_value(units *);
1116 : const char *get_string();
1117 : };
1118 :
1119 0 : bool high_water_mark_reg::get_value(units *res)
1120 : {
1121 0 : *res = curdiv->get_high_water_mark().to_units();
1122 0 : return true;
1123 : }
1124 :
1125 216 : const char *high_water_mark_reg::get_string()
1126 : {
1127 216 : return i_to_a(curdiv->get_high_water_mark().to_units());
1128 : }
1129 :
1130 : class distance_to_next_trap_reg : public reg {
1131 : public:
1132 : bool get_value(units *);
1133 : const char *get_string();
1134 : };
1135 :
1136 0 : bool distance_to_next_trap_reg::get_value(units *res)
1137 : {
1138 0 : *res = curdiv->distance_to_next_trap().to_units();
1139 0 : return true;
1140 : }
1141 :
1142 8358 : const char *distance_to_next_trap_reg::get_string()
1143 : {
1144 8358 : return i_to_a(curdiv->distance_to_next_trap().to_units());
1145 : }
1146 :
1147 : class diversion_name_reg : public reg {
1148 : public:
1149 : const char *get_string();
1150 : };
1151 :
1152 127653 : const char *diversion_name_reg::get_string()
1153 : {
1154 127653 : return curdiv->get_diversion_name();
1155 : }
1156 :
1157 : class next_trap_name_reg : public reg {
1158 : public:
1159 : const char *get_string();
1160 : };
1161 :
1162 4 : const char *next_trap_name_reg::get_string()
1163 : {
1164 4 : return curdiv->get_next_trap_name();
1165 : }
1166 :
1167 : class page_number_reg : public general_reg {
1168 : public:
1169 : page_number_reg();
1170 : bool get_value(units *);
1171 : void set_value(units);
1172 : };
1173 :
1174 1418 : page_number_reg::page_number_reg()
1175 : {
1176 1418 : }
1177 :
1178 35 : void page_number_reg::set_value(units n)
1179 : {
1180 35 : topdiv->set_page_number(n);
1181 35 : }
1182 :
1183 3504 : bool page_number_reg::get_value(units *res)
1184 : {
1185 3504 : *res = topdiv->get_page_number();
1186 3504 : return true;
1187 : }
1188 :
1189 : class next_page_number_reg : public reg {
1190 : public:
1191 : const char *get_string();
1192 : };
1193 :
1194 1 : const char *next_page_number_reg::get_string()
1195 : {
1196 1 : return i_to_a(topdiv->get_next_page_number());
1197 : }
1198 :
1199 : class page_ejecting_reg : public reg {
1200 : public:
1201 : const char *get_string();
1202 : };
1203 :
1204 12 : const char *page_ejecting_reg::get_string()
1205 : {
1206 12 : return i_to_a(topdiv->get_ejecting());
1207 : }
1208 :
1209 : class constant_vunits_reg : public reg {
1210 : vunits *p;
1211 : public:
1212 : constant_vunits_reg(vunits *);
1213 : const char *get_string();
1214 : };
1215 :
1216 2836 : constant_vunits_reg::constant_vunits_reg(vunits *q) : p(q)
1217 : {
1218 2836 : }
1219 :
1220 12 : const char *constant_vunits_reg::get_string()
1221 : {
1222 12 : return i_to_a(p->to_units());
1223 : }
1224 :
1225 : class nl_reg : public variable_reg {
1226 : public:
1227 : nl_reg();
1228 : void set_value(units);
1229 : };
1230 :
1231 1418 : nl_reg::nl_reg() : variable_reg(&nl_reg_contents)
1232 : {
1233 1418 : }
1234 :
1235 24 : void nl_reg::set_value(units n)
1236 : {
1237 24 : variable_reg::set_value(n);
1238 : // Setting nl to a negative value when the vertical position in
1239 : // the top-level diversion is 0 undoes the top of page transition,
1240 : // so that the header macro will be called as if the top of page
1241 : // transition hasn't happened. This is used by Larry Wall's
1242 : // wrapman program. Setting before_first_page_status to 2 rather than
1243 : // 1, tells top_level_diversion::begin_page not to call
1244 : // output_file::begin_page again.
1245 24 : if (n < 0 && topdiv->get_vertical_position() == V0)
1246 0 : topdiv->before_first_page_status = 2;
1247 24 : }
1248 :
1249 : class no_space_mode_reg : public reg {
1250 : public:
1251 : bool get_value(units *);
1252 : const char *get_string();
1253 : };
1254 :
1255 0 : bool no_space_mode_reg::get_value(units *val)
1256 : {
1257 0 : *val = curdiv->is_in_no_space_mode;
1258 0 : return true;
1259 : }
1260 :
1261 883 : const char *no_space_mode_reg::get_string()
1262 : {
1263 883 : return curdiv->is_in_no_space_mode ? "1" : "0";
1264 : }
1265 :
1266 1418 : void init_div_requests()
1267 : {
1268 1418 : init_request("box", box);
1269 1418 : init_request("boxa", box_append);
1270 1418 : init_request("bp", begin_page);
1271 1418 : init_request("ch", change_trap);
1272 1418 : init_request("da", divert_append);
1273 1418 : init_request("di", divert);
1274 1418 : init_request("dt", diversion_trap);
1275 1418 : init_request("fl", flush_request);
1276 1418 : init_request("mk", mark);
1277 1418 : init_request("ne", need_space);
1278 1418 : init_request("ns", no_space);
1279 1418 : init_request("os", output_saved_vertical_space);
1280 1418 : init_request("pl", configure_page_length_request);
1281 1418 : init_request("pn", page_number);
1282 1418 : init_request("po", configure_page_offset_request);
1283 1418 : init_request("pwh", print_traps);
1284 1418 : init_request("rs", restore_spacing);
1285 1418 : init_request("rt", return_request);
1286 1418 : init_request("sp", space_request);
1287 1418 : init_request("sv", save_vertical_space);
1288 1418 : init_request("vpt", vertical_position_traps);
1289 1418 : init_request("wh", when_request);
1290 1418 : register_dictionary.define(".a",
1291 1418 : new readonly_register(&last_post_line_extra_space));
1292 1418 : register_dictionary.define(".d", new vertical_position_reg);
1293 1418 : register_dictionary.define(".h", new high_water_mark_reg);
1294 1418 : register_dictionary.define(".ne",
1295 1418 : new constant_vunits_reg(&needed_space));
1296 1418 : register_dictionary.define(".ns", new no_space_mode_reg);
1297 1418 : register_dictionary.define(".o", new page_offset_reg);
1298 1418 : register_dictionary.define(".p", new page_length_reg);
1299 1418 : register_dictionary.define(".pe", new page_ejecting_reg);
1300 1418 : register_dictionary.define(".pn", new next_page_number_reg);
1301 1418 : register_dictionary.define(".t", new distance_to_next_trap_reg);
1302 1418 : register_dictionary.define(".trap", new next_trap_name_reg);
1303 1418 : register_dictionary.define(".trunc",
1304 1418 : new constant_vunits_reg(&truncated_space));
1305 1418 : register_dictionary.define(".vpt",
1306 1418 : new readonly_boolean_register(&honor_vertical_position_traps));
1307 1418 : register_dictionary.define(".z", new diversion_name_reg);
1308 1418 : register_dictionary.define("dl", new variable_reg(&dl_reg_contents));
1309 1418 : register_dictionary.define("dn", new variable_reg(&dn_reg_contents));
1310 1418 : register_dictionary.define("nl", new nl_reg);
1311 1418 : register_dictionary.define("%", new page_number_reg);
1312 1418 : }
1313 :
1314 : // Local Variables:
1315 : // fill-column: 72
1316 : // mode: C++
1317 : // End:
1318 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|