LCOV - code coverage report
Current view: top level - preproc/eqn - main.cpp (source / functions) Hit Total Coverage
Test: GNU roff Lines: 206 323 63.8 %
Date: 2026-01-16 17:51:41 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Copyright 1989-2025 Free Software Foundation, Inc.
       2             :      Written by James Clark (jjc@jclark.com)
       3             : 
       4             : This file is part of groff, the GNU roff typesetting system.
       5             : 
       6             : groff is free software; you can redistribute it and/or modify it under
       7             : the terms of the GNU General Public License as published by the Free
       8             : Software Foundation, either version 3 of the License, or
       9             : (at your option) any later version.
      10             : 
      11             : groff is distributed in the hope that it will be useful, but WITHOUT ANY
      12             : WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13             : FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14             : for more details.
      15             : 
      16             : You should have received a copy of the GNU General Public License
      17             : along with this program.  If not, see <http://www.gnu.org/licenses/>. */
      18             : 
      19             : #ifdef HAVE_CONFIG_H
      20             : #include <config.h>
      21             : #endif
      22             : 
      23             : #include <getopt.h> // getopt_long()
      24             : 
      25             : #include "eqn.h"
      26             : #include "stringclass.h"
      27             : #include "device.h"
      28             : #include "searchpath.h"
      29             : #include "macropath.h"
      30             : #include "htmlhint.h"
      31             : #include "pbox.h"
      32             : #include "ctype.h"
      33             : #include "lf.h"
      34             : 
      35             : #define STARTUP_FILE "eqnrc"
      36             : 
      37             : extern int yyparse();
      38             : extern "C" const char *Version_string;
      39             : 
      40             : static char *delim_search    (char *, int);
      41             : static int   inline_equation (FILE *, string &, string &);
      42             : 
      43             : char start_delim = '\0';
      44             : char end_delim = '\0';
      45             : int non_empty_flag;
      46             : int inline_flag;
      47             : int draw_flag = 0;
      48             : int one_size_reduction_flag = 0;
      49             : int compatible_flag = 0;
      50             : int no_newline_in_delim_flag = 0;
      51             : int html = 0;
      52             : int xhtml = 0;
      53             : eqnmode_t output_format;
      54             : 
      55           4 : static const char *input_char_description(int c)
      56             : {
      57           4 :   switch (c) {
      58           0 :   case '\001':
      59           0 :     return "a leader character";
      60           0 :   case '\n':
      61           0 :     return "a newline character";
      62           0 :   case '\b':
      63           0 :     return "a backspace character";
      64           0 :   case '\t':
      65           0 :     return "a tab character";
      66           0 :   case ' ':
      67           0 :     return "a space character";
      68           0 :   case '\177':
      69           0 :     return "a delete character";
      70             :   }
      71           4 :   const size_t bufsz = sizeof "character code " + INT_DIGITS + 1;
      72             :   static char buf[bufsz];
      73           4 :   (void) memset(buf, 0, bufsz);
      74           4 :   if (csprint(c)) {
      75           4 :     buf[0] = '\'';
      76           4 :     buf[1] = c;
      77           4 :     buf[2] = '\'';
      78           4 :     return buf;
      79             :   }
      80           0 :   (void) sprintf(buf, "character code %d", c);
      81           0 :   return buf;
      82             : }
      83             : 
      84      521161 : static bool read_line(FILE *fp, string *p)
      85             : {
      86      521161 :   p->clear();
      87      521161 :   int c = -1;
      88    10337315 :   while ((c = getc(fp)) != EOF) {
      89    10337208 :     *p += char(c);
      90    10337208 :     if (c == '\n')
      91      521054 :       break;
      92             :   }
      93      521161 :   return (p->length() > 0);
      94             : }
      95             : 
      96         109 : void do_file(FILE *fp, const char *filename)
      97             : {
      98         214 :   string linebuf;
      99         214 :   string str;
     100         109 :   string fn(filename);
     101         109 :   fn += '\0';
     102         109 :   normalize_file_name_for_lf_request(fn);
     103         109 :   current_lineno = 1;
     104         109 :   current_filename = fn.contents();
     105         109 :   if (output_format == troff)
     106         109 :     (void) printf(".lf %d %s%s\n", current_lineno,
     107         109 :         ('"' == current_filename[0]) ? "" : "\"", current_filename);
     108      510374 :   while (read_line(fp, &linebuf)) {
     109      510269 :     if (linebuf.length() >= 4
     110      445076 :         && linebuf[0] == '.' && linebuf[1] == 'l' && linebuf[2] == 'f'
     111      955345 :         && (linebuf[3] == ' ' || linebuf[3] == '\n' || compatible_flag))
     112             :     {
     113       21659 :       put_string(linebuf, stdout);
     114       21659 :       linebuf += '\0';
     115             :       // In GNU roff, `lf` assigns the number of the _next_ line.
     116       21659 :       if (interpret_lf_request_arguments(linebuf.contents() + 3))
     117       21655 :         current_lineno--;
     118             :     }
     119      488610 :     else if (linebuf.length() >= 4
     120      423417 :              && linebuf[0] == '.'
     121      295130 :              && linebuf[1] == 'E'
     122        6631 :              && linebuf[2] == 'Q'
     123      912027 :              && (linebuf[3] == ' ' || linebuf[3] == '\n'
     124           0 :                  || compatible_flag)) {
     125        3923 :       put_string(linebuf, stdout);
     126        3923 :       int start_lineno = current_lineno + 1;
     127        3923 :       str.clear();
     128             :       for (;;) {
     129       10784 :         if (!read_line(fp, &linebuf)) {
     130           1 :           current_lineno = 0; // suppress report of line number
     131           1 :           fatal("end of file before .EN");
     132             :         }
     133       10783 :         if (linebuf.length() >= 3
     134       10182 :             && linebuf[0] == '.'
     135       20965 :             && linebuf[1] == 'E') {
     136        3922 :           if (linebuf[2] == 'N'
     137        7842 :               && (linebuf.length() == 3 || linebuf[3] == ' '
     138        3920 :                   || linebuf[3] == '\n' || compatible_flag))
     139        3920 :             break;
     140           4 :           else if (linebuf[2] == 'Q' && linebuf.length() > 3
     141           4 :                    && (linebuf[3] == ' ' || linebuf[3] == '\n'
     142           0 :                        || compatible_flag)) {
     143           2 :             current_lineno++; // We just read another line.
     144           2 :             fatal("equations cannot be nested (.EQ within .EQ)");
     145             :           }
     146             :         }
     147        6861 :         str += linebuf;
     148             :       }
     149        3920 :       str += '\0';
     150        3920 :       start_string();
     151        3920 :       init_lex(str.contents(), current_filename, start_lineno);
     152        3920 :       non_empty_flag = 0;
     153        3920 :       inline_flag = 0;
     154        3920 :       yyparse();
     155        3920 :       restore_compatibility();
     156        3920 :       if (non_empty_flag) {
     157          32 :         if (output_format == mathml)
     158           0 :           putchar('\n');
     159             :         else {
     160          32 :           current_lineno++;
     161          32 :           printf(".lf %d\n", current_lineno);
     162          32 :           output_string();
     163             :         }
     164             :       }
     165        3920 :       if (output_format == troff) {
     166        3920 :         current_lineno++;
     167        3920 :         printf(".lf %d\n", current_lineno);
     168             :       }
     169        3920 :       put_string(linebuf, stdout);
     170             :     }
     171       13125 :     else if (start_delim != '\0' && linebuf.search(start_delim) >= 0
     172      497812 :              && inline_equation(fp, linebuf, str))
     173             :       ;
     174             :     else
     175      484541 :       put_string(linebuf, stdout);
     176      510265 :     current_lineno++;
     177             :   }
     178         105 :   current_filename = 0;
     179         105 :   current_lineno = 0;
     180         105 : }
     181             : 
     182             : // Handle an inline equation.  Return 1 if it was an inline equation,
     183             : // otherwise.
     184         146 : static int inline_equation(FILE *fp, string &linebuf, string &str)
     185             : {
     186         146 :   linebuf += '\0';
     187         146 :   char *ptr = &linebuf[0];
     188         146 :   char *start = delim_search(ptr, start_delim);
     189         146 :   if (!start) {
     190             :     // It wasn't a delimiter after all.
     191           0 :     linebuf.set_length(linebuf.length() - 1); // strip the '\0'
     192           0 :     return 0;
     193             :   }
     194         146 :   start_string();
     195         146 :   inline_flag = 1;
     196             :   for (;;) {
     197         154 :     if (no_newline_in_delim_flag && strchr(start + 1, end_delim) == 0) {
     198           1 :       error("unterminated inline equation; started with %1,"
     199             :             " expecting %2", input_char_description(start_delim),
     200           1 :             input_char_description(end_delim));
     201           1 :       char *nl = strchr(start + 1, '\n');
     202           1 :       if (nl != 0)
     203           1 :         *nl = '\0';
     204           1 :       do_text(ptr);
     205           1 :       break;
     206             :     }
     207         153 :     int start_lineno = current_lineno;
     208         153 :     *start = '\0';
     209         153 :     do_text(ptr);
     210         153 :     ptr = start + 1;
     211         153 :     str.clear();
     212             :     for (;;) {
     213         155 :       char *end = strchr(ptr, end_delim);
     214         155 :       if (end != 0) {
     215         152 :         *end = '\0';
     216         152 :         str += ptr;
     217         152 :         ptr = end + 1;
     218         152 :         break;
     219             :       }
     220           3 :       str += ptr;
     221           3 :       if (!read_line(fp, &linebuf))
     222           1 :         fatal("unterminated inline equation; started with %1,"
     223             :               " expecting %2", input_char_description(start_delim),
     224           1 :               input_char_description(end_delim));
     225           2 :       linebuf += '\0';
     226           2 :       ptr = &linebuf[0];
     227           2 :     }
     228         152 :     str += '\0';
     229         152 :     if (output_format == troff && html) {
     230           2 :       printf(".as1 %s ", LINE_STRING);
     231           2 :       html_begin_suppress();
     232           2 :       printf("\n");
     233             :     }
     234         152 :     init_lex(str.contents(), current_filename, start_lineno);
     235         152 :     yyparse();
     236         152 :     if (output_format == troff && html) {
     237           2 :       printf(".as1 %s ", LINE_STRING);
     238           2 :       html_end_suppress();
     239           2 :       printf("\n");
     240             :     }
     241         152 :     if (output_format == mathml)
     242           0 :       printf("\n");
     243         152 :     if (xhtml) {
     244             :       /* skip leading spaces */
     245           0 :       while ((*ptr != '\0') && (*ptr == ' '))
     246           0 :         ptr++;
     247             :     }
     248         152 :     start = delim_search(ptr, start_delim);
     249         152 :     if (start == 0) {
     250         144 :       char *nl = strchr(ptr, '\n');
     251         144 :       if (nl != 0)
     252         144 :         *nl = '\0';
     253         144 :       do_text(ptr);
     254         144 :       break;
     255             :     }
     256           8 :   }
     257         145 :   restore_compatibility();
     258         145 :   if (output_format == troff)
     259         145 :     printf(".lf %d\n", current_lineno);
     260         145 :   output_string();
     261         145 :   if (output_format == troff)
     262         145 :     printf(".lf %d\n", current_lineno + 1);
     263         145 :   return 1;
     264             : }
     265             : 
     266             : /* Search for delim.  Skip over number register and string names etc. */
     267             : 
     268        2170 : static char *delim_search(char *ptr, int delim)
     269             : {
     270        2170 :   while (*ptr) {
     271        2026 :     if (*ptr == delim)
     272         154 :       return ptr;
     273        1872 :     if (*ptr++ == '\\') {
     274          12 :       switch (*ptr) {
     275           0 :       case 'n':
     276             :       case '*':
     277             :       case 'f':
     278             :       case 'g':
     279             :       case 'k':
     280           0 :         switch (*++ptr) {
     281           0 :         case '\0':
     282             :         case '\\':
     283           0 :           break;
     284           0 :         case '(':
     285           0 :           if (*++ptr != '\\' && *ptr != '\0'
     286           0 :               && *++ptr != '\\' && *ptr != '\0')
     287           0 :               ptr++;
     288           0 :           break;
     289           0 :         case '[':
     290           0 :           while (*++ptr != '\0')
     291           0 :             if (*ptr == ']') {
     292           0 :               ptr++;
     293           0 :               break;
     294             :             }
     295           0 :           break;
     296           0 :         default:
     297           0 :           ptr++;
     298           0 :           break;
     299             :         }
     300           0 :         break;
     301           0 :       case '\\':
     302             :       case '\0':
     303           0 :         break;
     304          12 :       default:
     305          12 :         ptr++;
     306          12 :         break;
     307             :       }
     308             :     }
     309             :   }
     310         144 :   return 0;
     311             : }
     312             : 
     313           0 : void usage(FILE *stream)
     314             : {
     315           0 :   fprintf(stream,
     316             :     "usage: %s [-CNrR] [-d xy] [-f global-italic-font]"
     317             :     " [-m minimum-type-size] [-M eqnrc-directory]"
     318             :     " [-p super/subscript-size-reduction] [-s global-type-size]"
     319             :     " [-T device] [file ...]\n"
     320             :     "usage: %s {-v | --version}\n"
     321             :     "usage: %s --help\n",
     322             :     program_name, program_name, program_name);
     323           0 :   if (stdout == stream)
     324           0 :     fputs("\n"
     325             : "GNU eqn is a filter that translates expressions in its own language,\n"
     326             : "embedded in roff(7) input, into mathematical notation typeset by\n"
     327             : "GNU troff(1).  See the eqn(1) manual page.\n",
     328             :           stream);
     329           0 : }
     330             : 
     331          59 : int main(int argc, char **argv)
     332             : {
     333          59 :   program_name = argv[0];
     334             :   static char stderr_buf[BUFSIZ];
     335          59 :   setbuf(stderr, stderr_buf);
     336             :   int opt;
     337          59 :   bool want_startup_file = true;
     338             :   static const struct option long_options[] = {
     339             :     { "help", no_argument, 0 /* nullptr */, CHAR_MAX + 1 },
     340             :     { "version", no_argument, 0 /* nullptr */, 'v' },
     341             :     { 0 /* nullptr */, 0, 0 /* nullptr */, 0 }
     342             :   };
     343         247 :   while ((opt = getopt_long(argc, argv, ":CNrRd:f:m:M:p:s:T:v",
     344             :                             long_options, 0 /* nullptr */))
     345         247 :          != EOF)
     346         188 :     switch (opt) {
     347           0 :     case 'C':
     348           0 :       compatible_flag = 1;
     349           0 :       break;
     350           1 :     case 'R':                   // don't load eqnrc
     351           1 :       want_startup_file = false;
     352           1 :       break;
     353         129 :     case 'M':
     354         129 :       config_macro_path.command_line_dir(optarg);
     355         129 :       break;
     356           0 :     case 'v':
     357           0 :       printf("GNU eqn (groff) version %s\n", Version_string);
     358           0 :       exit(EXIT_SUCCESS);
     359             :       break;
     360           2 :     case 'd':
     361           2 :       if (optarg[0] == '\0' || optarg[1] == '\0')
     362           0 :         error("'-d' option requires a two-character argument");
     363           2 :       else if (is_invalid_input_char(optarg[0]))
     364           0 :         error("invalid delimiter (%1) in '-d' option argument",
     365           0 :               input_char_description(optarg[0]));
     366           2 :       else if (is_invalid_input_char(optarg[1]))
     367           0 :         error("invalid delimiter (%1) in '-d' option argument",
     368           0 :               input_char_description(optarg[1]));
     369             :       else {
     370           2 :         start_delim = optarg[0];
     371           2 :         end_delim = optarg[1];
     372             :       }
     373           2 :       break;
     374           0 :     case 'f':
     375           0 :       set_gifont(optarg);
     376           0 :       break;
     377          55 :     case 'T':
     378          55 :       device = optarg;
     379          55 :       if (strcmp(device, "ps:html") == 0) {
     380           3 :         device = "ps";
     381           3 :         html = 1;
     382             :       }
     383          52 :       else if (strcmp(device, "MathML") == 0) {
     384           0 :         output_format = mathml;
     385           0 :         want_startup_file = false;
     386             :       }
     387          52 :       else if (strcmp(device, "mathml:xhtml") == 0) {
     388           0 :         device = "MathML";
     389           0 :         output_format = mathml;
     390           0 :         want_startup_file = false;
     391           0 :         xhtml = 1;
     392             :       }
     393          55 :       break;
     394           0 :     case 's':
     395           0 :       if (set_gsize(optarg))
     396           0 :         warning("option '-s' is deprecated");
     397             :       else
     398           0 :         error("invalid size '%1' in '-s' option argument ", optarg);
     399           0 :       break;
     400           0 :     case 'p':
     401             :       {
     402             :         int n;
     403           0 :         if (sscanf(optarg, "%d", &n) == 1) {
     404           0 :           warning("option '-p' is deprecated");
     405           0 :           set_script_reduction(n);
     406             :         }
     407             :         else
     408           0 :           error("invalid size '%1' in '-p' option argument ", optarg);
     409             :       }
     410           0 :       break;
     411           0 :     case 'm':
     412             :       {
     413             :         int n;
     414           0 :         if (sscanf(optarg, "%d", &n) == 1)
     415           0 :           set_minimum_size(n);
     416             :         else
     417           0 :           error("invalid size '%1' in '-n' option argument", optarg);
     418             :       }
     419           0 :       break;
     420           0 :     case 'r':
     421           0 :       one_size_reduction_flag = 1;
     422           0 :       break;
     423           1 :     case 'N':
     424           1 :       no_newline_in_delim_flag = 1;
     425           1 :       break;
     426           0 :     case CHAR_MAX + 1: // --help
     427           0 :       usage(stdout);
     428           0 :       exit(EXIT_SUCCESS);
     429             :       break;
     430           0 :     case '?':
     431           0 :       if (optopt != 0)
     432           0 :         error("unrecognized command-line option '%1'", char(optopt));
     433             :       else
     434           0 :         error("unrecognized command-line option '%1'",
     435           0 :               argv[(optind - 1)]);
     436           0 :       usage(stderr);
     437           0 :       exit(2);
     438             :       break;
     439           0 :     case ':':
     440           0 :       error("command-line option '%1' requires an argument",
     441           0 :            char(optopt));
     442           0 :       usage(stderr);
     443           0 :       exit(2);
     444             :       break;
     445           0 :     default:
     446           0 :       assert(0 == "unhandled getopt_long return value");
     447             :     }
     448          59 :   init_table(device);
     449          59 :   init_char_table();
     450          59 :   init_param_table();
     451          59 :   atexit(free_param_table);
     452          59 :   printf(".do if !dEQ .ds EQ\n"
     453             :          ".do if !dEN .ds EN\n");
     454          59 :   if (output_format == troff) {
     455          59 :     printf(".if !'\\*(.T'%s' "
     456             :            // the html device uses '-Tps' to render equations as images
     457             :            ".if !'\\*(.T'html' "
     458             :            ".tm \\n[.F]: warning: %s should have been given a"
     459             :            " '-T\\*(.T' option\n", device, program_name);
     460          59 :     printf(".if '\\*(.T'html' "
     461             :            ".if !'%s'ps' "
     462             :            ".tmc \\n[.F]: warning: %s should have been given a '-Tps'"
     463             :            " option\n", device, program_name);
     464          59 :     printf(".if '\\*(.T'html' "
     465             :            ".if !'%s'ps' .tm1 \" (consider invoking"
     466             :            " 'groff -Thtml -e')\n", device);
     467             :   }
     468          59 :   if (want_startup_file) {
     469             :     char *path;
     470          58 :     FILE *fp = config_macro_path.open_file(STARTUP_FILE, &path);
     471          58 :     if (fp != 0 /* nullptr */) {
     472          50 :       do_file(fp, path);
     473          50 :       if (fclose(fp) < 0)
     474           0 :         fatal("cannot close '%1': %2", STARTUP_FILE, strerror(errno));
     475          50 :       free(path);
     476             :     }
     477             :     else
     478           8 :       error("cannot open startup file '%1': %2", STARTUP_FILE,
     479          16 :             strerror(errno));
     480             :   }
     481          59 :   if (optind >= argc)
     482          59 :     do_file(stdin, "-");
     483             :   else
     484           0 :     for (int i = optind; i < argc; i++)
     485           0 :       if (strcmp(argv[i], "-") == 0)
     486           0 :         do_file(stdin, "-");
     487             :       else {
     488           0 :         errno = 0;
     489           0 :         FILE *fp = fopen(argv[i], "r");
     490           0 :         if (!fp)
     491           0 :           fatal("cannot open '%1': %2", argv[i], strerror(errno));
     492             :         else {
     493           0 :           do_file(fp, argv[i]);
     494           0 :           if (fclose(fp) < 0)
     495           0 :             fatal("cannot close '%1': %2", argv[i], strerror(errno));
     496             :         }
     497             :       }
     498          55 :   if (ferror(stdout))
     499           0 :     fatal("standard output stream is in an error state");
     500          55 :   if (fflush(stdout) < 0)
     501           0 :     fatal("cannot flush standard output stream: %1", strerror(errno));
     502          55 :   exit(EXIT_SUCCESS);
     503             : }
     504             : 
     505             : // Local Variables:
     506             : // fill-column: 72
     507             : // mode: C++
     508             : // End:
     509             : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:

Generated by: LCOV version 1.14