1 // -*- C++ -*-
2 /* Copyright (C) 2003, 2004 Free Software Foundation, Inc.
3 Written by Gaius Mulley (gaius@glam.ac.uk)
4
5 This file is part of groff.
6
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING. If not, write to the Free Software
19 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
20
21 #define DEBUGGING
22
23 extern int debug_state;
24
25 #include "troff.h"
26 #include "hvunits.h"
27 #include "stringclass.h"
28 #include "mtsm.h"
29 #include "env.h"
30
31 static int no_of_statems = 0; // debugging aid
32
int_value()33 int_value::int_value()
34 : value(0), is_known(0)
35 {
36 }
37
~int_value()38 int_value::~int_value()
39 {
40 }
41
diff(FILE * fp,const char * s,int_value compare)42 void int_value::diff(FILE *fp, const char *s, int_value compare)
43 {
44 if (differs(compare)) {
45 fputs("x X ", fp);
46 fputs(s, fp);
47 fputs(" ", fp);
48 fputs(i_to_a(compare.value), fp);
49 fputs("\n", fp);
50 value = compare.value;
51 is_known = 1;
52 if (debug_state)
53 fflush(fp);
54 }
55 }
56
set(int v)57 void int_value::set(int v)
58 {
59 is_known = 1;
60 value = v;
61 }
62
unset()63 void int_value::unset()
64 {
65 is_known = 0;
66 }
67
set_if_unknown(int v)68 void int_value::set_if_unknown(int v)
69 {
70 if (!is_known)
71 set(v);
72 }
73
differs(int_value compare)74 int int_value::differs(int_value compare)
75 {
76 return compare.is_known
77 && (!is_known || value != compare.value);
78 }
79
bool_value()80 bool_value::bool_value()
81 {
82 }
83
~bool_value()84 bool_value::~bool_value()
85 {
86 }
87
diff(FILE * fp,const char * s,bool_value compare)88 void bool_value::diff(FILE *fp, const char *s, bool_value compare)
89 {
90 if (differs(compare)) {
91 fputs("x X ", fp);
92 fputs(s, fp);
93 fputs("\n", fp);
94 value = compare.value;
95 is_known = 1;
96 if (debug_state)
97 fflush(fp);
98 }
99 }
100
units_value()101 units_value::units_value()
102 {
103 }
104
~units_value()105 units_value::~units_value()
106 {
107 }
108
diff(FILE * fp,const char * s,units_value compare)109 void units_value::diff(FILE *fp, const char *s, units_value compare)
110 {
111 if (differs(compare)) {
112 fputs("x X ", fp);
113 fputs(s, fp);
114 fputs(" ", fp);
115 fputs(i_to_a(compare.value), fp);
116 fputs("\n", fp);
117 value = compare.value;
118 is_known = 1;
119 if (debug_state)
120 fflush(fp);
121 }
122 }
123
set(hunits v)124 void units_value::set(hunits v)
125 {
126 is_known = 1;
127 value = v.to_units();
128 }
129
differs(units_value compare)130 int units_value::differs(units_value compare)
131 {
132 return compare.is_known
133 && (!is_known || value != compare.value);
134 }
135
string_value()136 string_value::string_value()
137 : value(string("")), is_known(0)
138 {
139 }
140
~string_value()141 string_value::~string_value()
142 {
143 }
144
diff(FILE * fp,const char * s,string_value compare)145 void string_value::diff(FILE *fp, const char *s, string_value compare)
146 {
147 if (differs(compare)) {
148 fputs("x X ", fp);
149 fputs(s, fp);
150 fputs(" ", fp);
151 fputs(compare.value.contents(), fp);
152 fputs("\n", fp);
153 value = compare.value;
154 is_known = 1;
155 }
156 }
157
set(string v)158 void string_value::set(string v)
159 {
160 is_known = 1;
161 value = v;
162 }
163
unset()164 void string_value::unset()
165 {
166 is_known = 0;
167 }
168
differs(string_value compare)169 int string_value::differs(string_value compare)
170 {
171 return compare.is_known
172 && (!is_known || value != compare.value);
173 }
174
statem()175 statem::statem()
176 {
177 issue_no = no_of_statems;
178 no_of_statems++;
179 }
180
statem(statem * copy)181 statem::statem(statem *copy)
182 {
183 int i;
184 for (i = 0; i < LAST_BOOL; i++)
185 bool_values[i] = copy->bool_values[i];
186 for (i = 0; i < LAST_INT; i++)
187 int_values[i] = copy->int_values[i];
188 for (i = 0; i < LAST_UNITS; i++)
189 units_values[i] = copy->units_values[i];
190 for (i = 0; i < LAST_STRING; i++)
191 string_values[i] = copy->string_values[i];
192 issue_no = copy->issue_no;
193 }
194
~statem()195 statem::~statem()
196 {
197 }
198
flush(FILE * fp,statem * compare)199 void statem::flush(FILE *fp, statem *compare)
200 {
201 int_values[MTSM_FI].diff(fp, "devtag:.fi",
202 compare->int_values[MTSM_FI]);
203 int_values[MTSM_RJ].diff(fp, "devtag:.rj",
204 compare->int_values[MTSM_RJ]);
205 int_values[MTSM_SP].diff(fp, "devtag:.sp",
206 compare->int_values[MTSM_SP]);
207 units_values[MTSM_IN].diff(fp, "devtag:.in",
208 compare->units_values[MTSM_IN]);
209 units_values[MTSM_LL].diff(fp, "devtag:.ll",
210 compare->units_values[MTSM_LL]);
211 units_values[MTSM_PO].diff(fp, "devtag:.po",
212 compare->units_values[MTSM_PO]);
213 string_values[MTSM_TA].diff(fp, "devtag:.ta",
214 compare->string_values[MTSM_TA]);
215 units_values[MTSM_TI].diff(fp, "devtag:.ti",
216 compare->units_values[MTSM_TI]);
217 int_values[MTSM_CE].diff(fp, "devtag:.ce",
218 compare->int_values[MTSM_CE]);
219 bool_values[MTSM_EOL].diff(fp, "devtag:.eol",
220 compare->bool_values[MTSM_EOL]);
221 bool_values[MTSM_BR].diff(fp, "devtag:.br",
222 compare->bool_values[MTSM_BR]);
223 if (debug_state) {
224 fprintf(stderr, "compared state %d\n", compare->issue_no);
225 fflush(stderr);
226 }
227 }
228
add_tag(int_value_state t,int v)229 void statem::add_tag(int_value_state t, int v)
230 {
231 int_values[t].set(v);
232 }
233
add_tag(units_value_state t,hunits v)234 void statem::add_tag(units_value_state t, hunits v)
235 {
236 units_values[t].set(v);
237 }
238
add_tag(bool_value_state t)239 void statem::add_tag(bool_value_state t)
240 {
241 bool_values[t].set(1);
242 }
243
add_tag(string_value_state t,string v)244 void statem::add_tag(string_value_state t, string v)
245 {
246 string_values[t].set(v);
247 }
248
add_tag_if_unknown(int_value_state t,int v)249 void statem::add_tag_if_unknown(int_value_state t, int v)
250 {
251 int_values[t].set_if_unknown(v);
252 }
253
sub_tag_ce()254 void statem::sub_tag_ce()
255 {
256 int_values[MTSM_CE].unset();
257 }
258
259 /*
260 * add_tag_ta - add the tab settings to the minimum troff state machine
261 */
262
add_tag_ta()263 void statem::add_tag_ta()
264 {
265 if (is_html) {
266 string s = string("");
267 hunits d, l;
268 enum tab_type t;
269 do {
270 t = curenv->tabs.distance_to_next_tab(l, &d);
271 l += d;
272 switch (t) {
273 case TAB_LEFT:
274 s += " L ";
275 s += as_string(l.to_units());
276 break;
277 case TAB_CENTER:
278 s += " C ";
279 s += as_string(l.to_units());
280 break;
281 case TAB_RIGHT:
282 s += " R ";
283 s += as_string(l.to_units());
284 break;
285 case TAB_NONE:
286 break;
287 }
288 } while (t != TAB_NONE && l < curenv->get_line_length());
289 s += '\0';
290 string_values[MTSM_TA].set(s);
291 }
292 }
293
update(statem * older,statem * newer,int_value_state t)294 void statem::update(statem *older, statem *newer, int_value_state t)
295 {
296 if (newer->int_values[t].differs(older->int_values[t])
297 && !newer->int_values[t].is_known)
298 newer->int_values[t].set(older->int_values[t].value);
299 }
300
update(statem * older,statem * newer,units_value_state t)301 void statem::update(statem *older, statem *newer, units_value_state t)
302 {
303 if (newer->units_values[t].differs(older->units_values[t])
304 && !newer->units_values[t].is_known)
305 newer->units_values[t].set(older->units_values[t].value);
306 }
307
update(statem * older,statem * newer,bool_value_state t)308 void statem::update(statem *older, statem *newer, bool_value_state t)
309 {
310 if (newer->bool_values[t].differs(older->bool_values[t])
311 && !newer->bool_values[t].is_known)
312 newer->bool_values[t].set(older->bool_values[t].value);
313 }
314
update(statem * older,statem * newer,string_value_state t)315 void statem::update(statem *older, statem *newer, string_value_state t)
316 {
317 if (newer->string_values[t].differs(older->string_values[t])
318 && !newer->string_values[t].is_known)
319 newer->string_values[t].set(older->string_values[t].value);
320 }
321
merge(statem * newer,statem * older)322 void statem::merge(statem *newer, statem *older)
323 {
324 if (newer == 0 || older == 0)
325 return;
326 update(older, newer, MTSM_EOL);
327 update(older, newer, MTSM_BR);
328 update(older, newer, MTSM_FI);
329 update(older, newer, MTSM_LL);
330 update(older, newer, MTSM_PO);
331 update(older, newer, MTSM_RJ);
332 update(older, newer, MTSM_SP);
333 update(older, newer, MTSM_TA);
334 update(older, newer, MTSM_TI);
335 update(older, newer, MTSM_CE);
336 }
337
stack()338 stack::stack()
339 : next(0), state(0)
340 {
341 }
342
stack(statem * s,stack * n)343 stack::stack(statem *s, stack *n)
344 : next(n), state(s)
345 {
346 }
347
~stack()348 stack::~stack()
349 {
350 if (state)
351 delete state;
352 if (next)
353 delete next;
354 }
355
mtsm()356 mtsm::mtsm()
357 : sp(0)
358 {
359 driver = new statem();
360 }
361
~mtsm()362 mtsm::~mtsm()
363 {
364 delete driver;
365 if (sp)
366 delete sp;
367 }
368
369 /*
370 * push_state - push the current troff state and use `n' as
371 * the new troff state.
372 */
373
push_state(statem * n)374 void mtsm::push_state(statem *n)
375 {
376 if (is_html) {
377 #if defined(DEBUGGING)
378 if (debug_state)
379 fprintf(stderr, "--> state %d pushed\n", n->issue_no) ; fflush(stderr);
380 #endif
381 sp = new stack(n, sp);
382 }
383 }
384
pop_state()385 void mtsm::pop_state()
386 {
387 if (is_html) {
388 #if defined(DEBUGGING)
389 if (debug_state)
390 fprintf(stderr, "--> state popped\n") ; fflush(stderr);
391 #endif
392 if (sp == 0)
393 fatal("empty state machine stack");
394 if (sp->state)
395 delete sp->state;
396 sp->state = 0;
397 stack *t = sp;
398 sp = sp->next;
399 t->next = 0;
400 delete t;
401 }
402 }
403
404 /*
405 * inherit - scan the stack and collects inherited values.
406 */
407
inherit(statem * s,int reset_bool)408 void mtsm::inherit(statem *s, int reset_bool)
409 {
410 if (sp && sp->state) {
411 if (s->units_values[MTSM_IN].is_known
412 && sp->state->units_values[MTSM_IN].is_known)
413 s->units_values[MTSM_IN].value += sp->state->units_values[MTSM_IN].value;
414 s->update(sp->state, s, MTSM_FI);
415 s->update(sp->state, s, MTSM_LL);
416 s->update(sp->state, s, MTSM_PO);
417 s->update(sp->state, s, MTSM_RJ);
418 s->update(sp->state, s, MTSM_TA);
419 s->update(sp->state, s, MTSM_TI);
420 s->update(sp->state, s, MTSM_CE);
421 if (sp->state->bool_values[MTSM_BR].is_known
422 && sp->state->bool_values[MTSM_BR].value) {
423 if (reset_bool)
424 sp->state->bool_values[MTSM_BR].set(0);
425 s->bool_values[MTSM_BR].set(1);
426 if (debug_state)
427 fprintf(stderr, "inherited br from pushed state %d\n",
428 sp->state->issue_no);
429 }
430 else if (s->bool_values[MTSM_BR].is_known
431 && s->bool_values[MTSM_BR].value)
432 if (! s->int_values[MTSM_CE].is_known)
433 s->bool_values[MTSM_BR].unset();
434 if (sp->state->bool_values[MTSM_EOL].is_known
435 && sp->state->bool_values[MTSM_EOL].value) {
436 if (reset_bool)
437 sp->state->bool_values[MTSM_EOL].set(0);
438 s->bool_values[MTSM_EOL].set(1);
439 }
440 }
441 }
442
flush(FILE * fp,statem * s,string tag_list)443 void mtsm::flush(FILE *fp, statem *s, string tag_list)
444 {
445 if (is_html && s) {
446 inherit(s, 1);
447 driver->flush(fp, s);
448 // Set rj, ce, ti to unknown if they were known and
449 // we have seen an eol or br. This ensures that these values
450 // are emitted during the next glyph (as they step from n..0
451 // at each newline).
452 if ((driver->bool_values[MTSM_EOL].is_known
453 && driver->bool_values[MTSM_EOL].value)
454 || (driver->bool_values[MTSM_BR].is_known
455 && driver->bool_values[MTSM_BR].value)) {
456 if (driver->units_values[MTSM_TI].is_known)
457 driver->units_values[MTSM_TI].is_known = 0;
458 if (driver->int_values[MTSM_RJ].is_known
459 && driver->int_values[MTSM_RJ].value > 0)
460 driver->int_values[MTSM_RJ].is_known = 0;
461 if (driver->int_values[MTSM_CE].is_known
462 && driver->int_values[MTSM_CE].value > 0)
463 driver->int_values[MTSM_CE].is_known = 0;
464 }
465 // reset the boolean values
466 driver->bool_values[MTSM_BR].set(0);
467 driver->bool_values[MTSM_EOL].set(0);
468 // reset space value
469 driver->int_values[MTSM_SP].set(0);
470 // lastly write out any direct tag entries
471 if (tag_list != string("")) {
472 string t = tag_list + '\0';
473 fputs(t.contents(), fp);
474 }
475 }
476 }
477
478 /*
479 * display_state - dump out a synopsis of the state to stderr.
480 */
481
display_state()482 void statem::display_state()
483 {
484 fprintf(stderr, " <state ");
485 if (bool_values[MTSM_BR].is_known) {
486 if (bool_values[MTSM_BR].value)
487 fprintf(stderr, "[br]");
488 else
489 fprintf(stderr, "[!br]");
490 }
491 if (bool_values[MTSM_EOL].is_known) {
492 if (bool_values[MTSM_EOL].value)
493 fprintf(stderr, "[eol]");
494 else
495 fprintf(stderr, "[!eol]");
496 }
497 if (int_values[MTSM_SP].is_known) {
498 if (int_values[MTSM_SP].value)
499 fprintf(stderr, "[sp %d]", int_values[MTSM_SP].value);
500 else
501 fprintf(stderr, "[!sp]");
502 }
503 fprintf(stderr, ">");
504 fflush(stderr);
505 }
506
has_changed(int_value_state t,statem * s)507 int mtsm::has_changed(int_value_state t, statem *s)
508 {
509 return driver->int_values[t].differs(s->int_values[t]);
510 }
511
has_changed(units_value_state t,statem * s)512 int mtsm::has_changed(units_value_state t, statem *s)
513 {
514 return driver->units_values[t].differs(s->units_values[t]);
515 }
516
has_changed(bool_value_state t,statem * s)517 int mtsm::has_changed(bool_value_state t, statem *s)
518 {
519 return driver->bool_values[t].differs(s->bool_values[t]);
520 }
521
has_changed(string_value_state t,statem * s)522 int mtsm::has_changed(string_value_state t, statem *s)
523 {
524 return driver->string_values[t].differs(s->string_values[t]);
525 }
526
changed(statem * s)527 int mtsm::changed(statem *s)
528 {
529 if (s == 0 || !is_html)
530 return 0;
531 s = new statem(s);
532 inherit(s, 0);
533 int result = has_changed(MTSM_EOL, s)
534 || has_changed(MTSM_BR, s)
535 || has_changed(MTSM_FI, s)
536 || has_changed(MTSM_IN, s)
537 || has_changed(MTSM_LL, s)
538 || has_changed(MTSM_PO, s)
539 || has_changed(MTSM_RJ, s)
540 || has_changed(MTSM_SP, s)
541 || has_changed(MTSM_TA, s)
542 || has_changed(MTSM_CE, s);
543 delete s;
544 return result;
545 }
546
add_tag(FILE * fp,string s)547 void mtsm::add_tag(FILE *fp, string s)
548 {
549 fflush(fp);
550 s += '\0';
551 fputs(s.contents(), fp);
552 }
553
554 /*
555 * state_set class
556 */
557
state_set()558 state_set::state_set()
559 : boolset(0), intset(0), unitsset(0), stringset(0)
560 {
561 }
562
~state_set()563 state_set::~state_set()
564 {
565 }
566
incl(bool_value_state b)567 void state_set::incl(bool_value_state b)
568 {
569 boolset |= 1 << (int)b;
570 }
571
incl(int_value_state i)572 void state_set::incl(int_value_state i)
573 {
574 intset |= 1 << (int)i;
575 }
576
incl(units_value_state u)577 void state_set::incl(units_value_state u)
578 {
579 unitsset |= 1 << (int)u;
580 }
581
incl(string_value_state s)582 void state_set::incl(string_value_state s)
583 {
584 stringset |= 1 << (int)s;
585 }
586
excl(bool_value_state b)587 void state_set::excl(bool_value_state b)
588 {
589 boolset &= ~(1 << (int)b);
590 }
591
excl(int_value_state i)592 void state_set::excl(int_value_state i)
593 {
594 intset &= ~(1 << (int)i);
595 }
596
excl(units_value_state u)597 void state_set::excl(units_value_state u)
598 {
599 unitsset &= ~(1 << (int)u);
600 }
601
excl(string_value_state s)602 void state_set::excl(string_value_state s)
603 {
604 stringset &= ~(1 << (int)s);
605 }
606
is_in(bool_value_state b)607 int state_set::is_in(bool_value_state b)
608 {
609 return (boolset & (1 << (int)b)) != 0;
610 }
611
is_in(int_value_state i)612 int state_set::is_in(int_value_state i)
613 {
614 return (intset & (1 << (int)i)) != 0;
615 }
616
617 // Note: this used to have a bug s.t. it always tested for bit 0 (benl 18/5/11)
is_in(units_value_state u)618 int state_set::is_in(units_value_state u)
619 {
620 return (unitsset & (1 << (int)u)) != 0;
621 }
622
623 // Note: this used to have a bug s.t. it always tested for bit 0 (benl 18/5/11)
is_in(string_value_state s)624 int state_set::is_in(string_value_state s)
625 {
626 return (stringset & (1 << (int)s)) != 0;
627 }
628
add(units_value_state,int n)629 void state_set::add(units_value_state, int n)
630 {
631 unitsset += n;
632 }
633
val(units_value_state)634 units state_set::val(units_value_state)
635 {
636 return unitsset;
637 }
638