LCOV - code coverage report
Current view: top level - roff/troff - reg.cpp (source / functions) Hit Total Coverage
Test: GNU roff Lines: 191 325 58.8 %
Date: 2026-01-16 17:51:41 Functions: 23 36 63.9 %
Legend: Lines: hit not hit

          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:

Generated by: LCOV version 1.14