Line data Source code
1 : /* Copyright 1989-2020 Free Software Foundation, Inc.
2 : 2021-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 : #ifdef HAVE_CONFIG_H
22 : #include <config.h>
23 : #endif
24 :
25 : #include <assert.h>
26 :
27 : #include "troff.h"
28 : #include "dictionary.h"
29 : #include "lib.h" // INT_DIGITS
30 : #include "token.h"
31 : #include "request.h"
32 : #include "reg.h"
33 :
34 : object_dictionary register_dictionary(101);
35 :
36 0 : bool reg::get_value(units * /*d*/)
37 : {
38 0 : return false;
39 : }
40 :
41 0 : void reg::increment()
42 : {
43 0 : error("cannot increment read-only register");
44 0 : }
45 :
46 0 : void reg::decrement()
47 : {
48 0 : error("cannot decrement read-only register");
49 0 : }
50 :
51 0 : void reg::set_increment(units /*n*/)
52 : {
53 0 : error("cannot automatically increment read-only register");
54 0 : }
55 :
56 0 : int reg::get_increment() const
57 : {
58 0 : return 0;
59 : }
60 :
61 0 : void reg::alter_format(char /*f*/, int /*w*/)
62 : {
63 0 : error("cannot assign format of read-only register");
64 0 : }
65 :
66 0 : const char *reg::get_format()
67 : {
68 0 : return "0";
69 : }
70 :
71 0 : void reg::set_value(units /*n*/)
72 : {
73 0 : error("cannot write read-only register");
74 0 : }
75 :
76 0 : bool reg::can_autoincrement() const
77 : {
78 0 : return false;
79 : }
80 :
81 257305 : general_reg::general_reg() : format('1'), width(0), inc(0)
82 : {
83 257305 : }
84 :
85 : static char uppercase_array[] = {
86 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
87 : 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
88 : 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
89 : 'Y', 'Z',
90 : };
91 :
92 : static char lowercase_array[] = {
93 : 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
94 : 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
95 : 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
96 : 'y', 'z',
97 : };
98 :
99 7586727 : static const char *number_value_to_ascii(int value, char format,
100 : int width)
101 : {
102 : static char buf[128]; // must be at least 21
103 7586727 : switch (format) {
104 7586644 : case '1':
105 7586644 : if (width <= 0)
106 7585155 : return i_to_a(value);
107 1489 : else if (width > int((sizeof buf) - 2))
108 0 : sprintf(buf, "%.*d", int((sizeof buf) - 2), int(value));
109 : else
110 1489 : sprintf(buf, "%.*d", width, int(value));
111 1489 : break;
112 42 : case 'i':
113 : case 'I':
114 : {
115 42 : char *p = buf;
116 : // troff uses z and w to represent 10000 and 5000 in Roman
117 : // numerals; I can find no historical basis for this usage
118 42 : const char *s = format == 'i' ? "zwmdclxvi" : "ZWMDCLXVI";
119 42 : int n = int(value);
120 42 : if (n >= 40000 || n <= -40000) {
121 0 : error("magnitude of '%1' too big for i or I format", n);
122 0 : return i_to_a(n);
123 : }
124 42 : if (n == 0) {
125 0 : *p++ = '0';
126 0 : *p = '\0';
127 0 : break;
128 : }
129 42 : if (n < 0) {
130 0 : *p++ = '-';
131 0 : n = -n;
132 : }
133 42 : while (n >= 10000) {
134 0 : *p++ = s[0];
135 0 : n -= 10000;
136 : }
137 210 : for (int i = 1000; i > 0; i /= 10, s += 2) {
138 168 : int m = n/i;
139 168 : n -= m*i;
140 : switch (m) {
141 1 : case 3:
142 1 : *p++ = s[2];
143 : /* falls through */
144 5 : case 2:
145 5 : *p++ = s[2];
146 : /* falls through */
147 40 : case 1:
148 40 : *p++ = s[2];
149 40 : break;
150 1 : case 4:
151 1 : *p++ = s[2];
152 1 : *p++ = s[1];
153 1 : break;
154 0 : case 8:
155 0 : *p++ = s[1];
156 0 : *p++ = s[2];
157 0 : *p++ = s[2];
158 0 : *p++ = s[2];
159 0 : break;
160 0 : case 7:
161 0 : *p++ = s[1];
162 0 : *p++ = s[2];
163 0 : *p++ = s[2];
164 0 : break;
165 0 : case 6:
166 0 : *p++ = s[1];
167 0 : *p++ = s[2];
168 0 : break;
169 1 : case 5:
170 1 : *p++ = s[1];
171 1 : break;
172 0 : case 9:
173 0 : *p++ = s[2];
174 0 : *p++ = s[0];
175 : }
176 : }
177 42 : *p = '\0';
178 42 : break;
179 : }
180 41 : case 'a':
181 : case 'A':
182 : {
183 41 : int n = value;
184 41 : char *p = buf;
185 41 : if (n == 0) {
186 0 : *p++ = '0';
187 0 : *p = '\0';
188 : }
189 : else {
190 41 : if (n < 0) {
191 0 : n = -n;
192 0 : *p++ = '-';
193 : }
194 : // this is a bit tricky
195 82 : while (n > 0) {
196 41 : int d = n % 26;
197 41 : if (d == 0)
198 0 : d = 26;
199 41 : n -= d;
200 41 : n /= 26;
201 63 : *p++ = format == 'a' ? lowercase_array[d - 1] :
202 22 : uppercase_array[d - 1];
203 : }
204 41 : *p-- = '\0';
205 41 : char *q = buf[0] == '-' ? buf + 1 : buf;
206 41 : while (q < p) {
207 0 : char temp = *q;
208 0 : *q = *p;
209 0 : *p = temp;
210 0 : --p;
211 0 : ++q;
212 : }
213 : }
214 41 : break;
215 : }
216 0 : default:
217 0 : assert(0 == "unhandled case of register format");
218 : break;
219 : }
220 1572 : return buf;
221 : }
222 :
223 7586727 : const char *general_reg::get_string()
224 : {
225 : units n;
226 7586727 : if (!get_value(&n))
227 0 : return "";
228 7586727 : return number_value_to_ascii(n, format, width);
229 : }
230 :
231 476085 : void general_reg::increment()
232 : {
233 : int n;
234 476085 : if (get_value(&n))
235 476085 : set_value(n + inc);
236 476085 : }
237 :
238 98022 : void general_reg::decrement()
239 : {
240 : int n;
241 98022 : if (get_value(&n))
242 98022 : set_value(n - inc);
243 98022 : }
244 :
245 101960 : void general_reg::set_increment(units n)
246 : {
247 101960 : inc = n;
248 101960 : }
249 :
250 0 : int general_reg::get_increment() const
251 : {
252 0 : return inc;
253 : }
254 :
255 0 : bool general_reg::can_autoincrement() const
256 : {
257 0 : return true;
258 : }
259 :
260 1666 : void general_reg::alter_format(char f, int w)
261 : {
262 1666 : format = f;
263 1666 : width = w;
264 1666 : }
265 :
266 623 : static const char *number_format_to_ascii(char format, int width)
267 : {
268 : static char buf[24];
269 623 : if (format == '1') {
270 616 : if (width > 0) {
271 519 : int n = width;
272 519 : if (n > int(sizeof buf) - 1)
273 0 : n = int(sizeof buf) - 1;
274 519 : sprintf(buf, "%.*d", n, 0);
275 519 : return buf;
276 : }
277 : else
278 97 : return "0";
279 : }
280 : else {
281 7 : buf[0] = format;
282 7 : buf[1] = '\0';
283 7 : return buf;
284 : }
285 : }
286 :
287 623 : const char *general_reg::get_format()
288 : {
289 623 : return number_format_to_ascii(format, width);
290 : }
291 :
292 : class number_reg : public general_reg {
293 : units value;
294 : public:
295 : number_reg();
296 : bool get_value(units *);
297 : void set_value(units);
298 : };
299 :
300 219019 : number_reg::number_reg() : value(0)
301 : {
302 219019 : }
303 :
304 10114546 : bool number_reg::get_value(units *res)
305 : {
306 10114546 : *res = value;
307 10114546 : return true;
308 : }
309 :
310 2984676 : void number_reg::set_value(units n)
311 : {
312 2984676 : value = n;
313 2984676 : }
314 :
315 34032 : variable_reg::variable_reg(units *p) : ptr(p)
316 : {
317 34032 : }
318 :
319 36494 : void variable_reg::set_value(units n)
320 : {
321 36494 : *ptr = n;
322 36494 : }
323 :
324 134113 : bool variable_reg::get_value(units *res)
325 : {
326 134113 : *res = *ptr;
327 134113 : return true;
328 : }
329 :
330 2263248 : static void define_register_request()
331 : {
332 2263248 : if (!has_arg()) {
333 0 : warning(WARN_MISSING, "register definition request expects"
334 : " arguments");
335 0 : skip_line();
336 0 : return;
337 : }
338 2263248 : symbol nm = read_identifier();
339 2263248 : if (nm.is_null()) {
340 0 : skip_line();
341 0 : return;
342 : }
343 2263248 : reg *r = static_cast<reg *>(register_dictionary.lookup(nm));
344 : units v;
345 : units prev_value;
346 2263248 : if ((0 /* nullptr */ == r) || !r->get_value(&prev_value))
347 178120 : prev_value = 0;
348 2263248 : if (!has_arg()) {
349 0 : warning(WARN_MISSING, "register definition request expects"
350 : " a numeric expression as second argument");
351 0 : skip_line();
352 0 : return;
353 : }
354 : // TODO: grochar
355 2263248 : if (read_measurement(&v, (unsigned char)('u'), prev_value)) {
356 2263248 : if (0 /* nullptr */ == r) {
357 178120 : r = new number_reg;
358 178120 : register_dictionary.define(nm, r);
359 : }
360 2263248 : r->set_value(v);
361 2263248 : if (tok.is_space()) {
362 : // TODO: grochar
363 213527 : if (has_arg() && read_measurement(&v, (unsigned char)('u')))
364 101960 : r->set_increment(v);
365 : }
366 2049721 : else if (has_arg() && !tok.is_tab())
367 0 : warning(WARN_SYNTAX, "expected end of line or an auto-increment"
368 : " argument in register definition request; got %1",
369 0 : tok.description());
370 : }
371 2263248 : skip_line();
372 : }
373 :
374 : #if 0
375 : void inline_define_register()
376 : {
377 : token start_token;
378 : start_token.next();
379 : if (!start_token.is_usable_as_delimiter(true /* report error */))
380 : return;
381 : tok.next();
382 : symbol nm = read_identifier(true /* required */);
383 : if (nm.is_null())
384 : return;
385 : reg *r = static_cast<reg *>(register_dictionary.lookup(nm));
386 : if (0 /* nullptr */ == r) {
387 : r = new number_reg;
388 : register_dictionary.define(nm, r);
389 : }
390 : units v;
391 : units prev_value;
392 : if ((0 /* nullptr */ == r) || !r->get_value(&prev_value))
393 : prev_value = 0;
394 : if (read_measurement(&v, 'u', prev_value)) {
395 : r->set_value(v);
396 : if (start_token != tok) {
397 : if (read_measurement(&v, 'u')) {
398 : r->set_increment(v);
399 : if (start_token != tok) {
400 : // token::description() writes to static, class-wide storage,
401 : // so we must allocate a copy of it before issuing the next
402 : // diagnostic.
403 : char *delimdesc = strdup(start_token.description());
404 : warning(WARN_DELIM, "closing delimiter does not match;"
405 : " expected %1, got %2", delimdesc, tok.description());
406 : free(delimdesc);
407 : }
408 : }
409 : }
410 : }
411 : }
412 : #endif
413 :
414 39913 : void set_register(symbol nm, units n)
415 : {
416 39913 : reg *r = static_cast<reg *>(register_dictionary.lookup(nm));
417 39913 : if (0 /* nullptr */ == r) {
418 20600 : r = new number_reg;
419 20600 : register_dictionary.define(nm, r);
420 : }
421 39913 : r->set_value(n);
422 39913 : }
423 :
424 8672390 : reg *look_up_register(symbol nm, bool suppress_creation)
425 : {
426 8672390 : reg *r = static_cast<reg *>(register_dictionary.lookup(nm));
427 8672390 : if ((0 /* nullptr */ == r) && !suppress_creation) {
428 20158 : warning(WARN_REG, "register '%1' not defined", nm.contents());
429 20158 : r = new number_reg;
430 20158 : register_dictionary.define(nm, r);
431 : }
432 8672390 : return r;
433 : }
434 :
435 1666 : static void assign_register_format_request()
436 : {
437 1666 : if (!has_arg()) {
438 0 : warning(WARN_MISSING, "register interpolation format assignment"
439 : " request expects arguments");
440 0 : skip_line();
441 0 : return;
442 : }
443 1666 : symbol nm = read_identifier();
444 1666 : if (nm.is_null()) {
445 0 : skip_line();
446 0 : return;
447 : }
448 1666 : reg *r = static_cast<reg *>(register_dictionary.lookup(nm));
449 1666 : if (0 /* nullptr */ == r) {
450 141 : r = new number_reg;
451 141 : register_dictionary.define(nm, r);
452 : }
453 1666 : tok.skip_spaces();
454 1666 : int c = tok.ch(); // safely compares to char literals; TODO: grochar
455 1666 : if (csdigit(c)) {
456 1355 : int n = 0;
457 276 : do {
458 1631 : ++n;
459 1631 : tok.next();
460 1631 : } while (csdigit(tok.ch()));
461 1355 : r->alter_format('1', n);
462 : }
463 311 : else if ((c == int('i'))
464 168 : || (c == int('I'))
465 167 : || (c == int('a'))
466 67 : || (c == int('A'))) // TODO: grochar * 4
467 311 : r->alter_format(c);
468 0 : else if (!has_arg())
469 0 : warning(WARN_MISSING, "register interpolation format assignment"
470 : " request register format as second argument");
471 : else
472 0 : error("register interpolation format assignment request expects"
473 : " 'i', 'I', 'a', 'A', or decimal digits, got %1",
474 0 : tok.description());
475 1666 : skip_line();
476 : }
477 :
478 126854 : static void remove_register_request()
479 : {
480 126854 : if (!has_arg()) {
481 0 : warning(WARN_MISSING, "register removal request expects arguments");
482 0 : skip_line();
483 0 : return;
484 : }
485 : for (;;) {
486 126893 : symbol s = read_identifier();
487 126893 : if (s.is_null())
488 0 : break;
489 126893 : register_dictionary.remove(s);
490 126893 : if (!has_arg())
491 126854 : break;
492 39 : }
493 126854 : skip_line();
494 : }
495 :
496 2631 : static void alias_register_request()
497 : {
498 2631 : if (!has_arg()) {
499 0 : warning(WARN_MISSING, "register aliasing request expects"
500 : " arguments");
501 0 : skip_line();
502 0 : return;
503 : }
504 2631 : symbol s1 = read_identifier();
505 2631 : if (!s1.is_null()) {
506 2631 : if (!has_arg())
507 0 : warning(WARN_MISSING, "register aliasing request expects"
508 : " identifier of existing register as second argument");
509 : else {
510 2631 : symbol s2 = read_identifier();
511 2631 : if (!s2.is_null()) {
512 2631 : if (!register_dictionary.alias(s1, s2))
513 0 : error("cannot alias undefined register '%1'", s2.contents());
514 : }
515 : }
516 : }
517 2631 : skip_line();
518 : }
519 :
520 6 : static void rename_register_request()
521 : {
522 6 : if (!has_arg()) {
523 0 : warning(WARN_MISSING, "register renaming request expects"
524 : " arguments");
525 0 : skip_line();
526 0 : return;
527 : }
528 6 : symbol s1 = read_identifier();
529 6 : if (!has_arg())
530 0 : warning(WARN_MISSING, "register renaming request exepects new"
531 : " identifier as second argument");
532 6 : else if (!s1.is_null()) {
533 6 : symbol s2 = read_identifier();
534 6 : if (!s2.is_null())
535 6 : register_dictionary.rename(s1, s2);
536 : }
537 6 : skip_line();
538 : }
539 :
540 0 : static void dump_register(symbol *id, reg *r)
541 : {
542 : int n;
543 0 : const size_t sz = INT_DIGITS + 1 /* leading sign */;
544 : char inc[sz];
545 0 : errprint("%1\t", id->contents());
546 0 : if (r->get_value(&n)) {
547 0 : errprint("%1", n);
548 0 : if (r->can_autoincrement()) {
549 0 : (void) snprintf(inc, sz, "%+d", r->get_increment());
550 0 : errprint("\t%1", inc);
551 : }
552 0 : const char *f = r->get_format();
553 0 : assert(f != 0 /* nullptr */);
554 0 : if (f != 0 /* nullptr*/)
555 0 : errprint("\t%1", f);
556 : }
557 : else {
558 0 : const char *s = r->get_string();
559 : // Some string-valued registers, like `.z` and `.itm`, can be empty.
560 0 : if (s != 0 /* nullptr */)
561 0 : errprint("%1", s);
562 : }
563 0 : errprint("\n");
564 0 : }
565 :
566 0 : static void print_register_request()
567 : {
568 : reg *r;
569 0 : symbol identifier;
570 0 : if (has_arg()) {
571 0 : do {
572 0 : identifier = read_identifier();
573 0 : r = look_up_register(identifier, true /* suppress creation */);
574 0 : if (r != 0 /* nullptr */)
575 0 : dump_register(&identifier, r);
576 0 : } while (has_arg());
577 : }
578 : else {
579 0 : object_dictionary_iterator iter(register_dictionary);
580 : // We must use the nuclear `reinterpret_cast` operator because GNU
581 : // troff's dictionary types use a pre-STL approach to containers.
582 0 : while (iter.get(&identifier, reinterpret_cast<object **>(&r))) {
583 0 : assert(!identifier.is_null());
584 0 : dump_register(&identifier, r);
585 : }
586 : }
587 0 : fflush(stderr);
588 0 : skip_line();
589 0 : }
590 :
591 1418 : void init_reg_requests()
592 : {
593 1418 : init_request("rr", remove_register_request);
594 1418 : init_request("nr", define_register_request);
595 1418 : init_request("af", assign_register_format_request);
596 1418 : init_request("aln", alias_register_request);
597 1418 : init_request("rnn", rename_register_request);
598 1418 : init_request("pnr", print_register_request);
599 1418 : }
600 :
601 : // Local Variables:
602 : // fill-column: 72
603 : // mode: C++
604 : // End:
605 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|