1 /* 2 * Copyright (C) 1984-2026 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 #include "less.h" 11 12 struct lmsg_sym { lessmsg_id id; constant char *sym; }; 13 14 /* 15 * lmsgs is the array of all messages, indexed by lmsg id. 16 * Initialize it to the default messages. 17 * Individual message can be overridden by a custom lessmsg file. 18 */ 19 #undef M 20 #define M(sym,msg) msg, 21 static char *lmsgs[] = { 22 NULL, /* id 0 (LM_NULL) is not in lessmsg.inc */ 23 #include "lessmsg.inc" 24 }; 25 26 /* 27 * lmsg_syms is the map of symbol names to lmsg ids. 28 */ 29 #undef M 30 #define M(sym,msg) { LM_##sym, #sym }, 31 static constant struct lmsg_sym lmsg_syms[] = { 32 #include "lessmsg.inc" 33 M(HELP,"") 34 }; 35 36 /* 37 * For historical reasons the help file is stored separately rather than 38 * in the lmsgs array but it can still be overridden by a lessmsg file. 39 */ 40 public char *help_data; 41 public int size_help_data; 42 extern char helpdata[]; 43 extern int size_helpdata; 44 45 /* 46 * Report an error in a lessmsg file. 47 */ 48 static void lmsg_error(constant char *err, constant char *lmsg_file, int linenum) 49 { 50 PARG parg[3]; 51 parg[0].p_string = lmsg_file; 52 parg[1].p_int = linenum; 53 parg[2].p_string = err; 54 error("Error in lessmsg file %s line %d: %s", parg); 55 } 56 57 /* 58 * Return a numeric lmsg id given a symbolic name. 59 */ 60 static lessmsg_id lmsg_id(constant char *sym) 61 { 62 size_t i; 63 for (i = 0; i < countof(lmsg_syms); i++) 64 if (strcmp(sym, lmsg_syms[i].sym) == 0) 65 return lmsg_syms[i].id; 66 return LM_NULL; 67 } 68 69 /* 70 * Return a numeric char value from a string. 71 */ 72 static char numeric_char(char **pp, int radix, constant char *lmsg_file, int linenum) 73 { 74 constant int max_chars = (radix == 16) ? 2 : 3; 75 char nbuf[4]; /* max_chars+1 */ 76 char *p = *pp; 77 int val; 78 strncpy(nbuf, p, max_chars); 79 nbuf[max_chars] = '\0'; 80 val = lstrtoi(nbuf, &p, radix); 81 if (p == nbuf || val < 0 || val > 255) 82 { 83 lmsg_error("invalid numeric char value", lmsg_file, linenum); 84 return '?'; 85 } 86 *pp += ptr_diff(p,nbuf); 87 return (char) val; 88 } 89 90 /* 91 * Process one character in a lessmsg file. 92 */ 93 static char * lmsg_char(char *mp, char *line, size_t line_size, FILE* fd, struct xbuffer *xbuf, constant char *lmsg_file, int *linenum) 94 { 95 if (*mp != '\\') 96 xbuf_add_char(xbuf, *mp++); 97 else 98 { 99 /* Handle backslash escape. */ 100 switch (*++mp) 101 { 102 case '\0': break; 103 case 'n': xbuf_add_char(xbuf, '\n'); ++mp; break; 104 case 'r': xbuf_add_char(xbuf, '\r'); ++mp; break; 105 case 't': xbuf_add_char(xbuf, '\t'); ++mp; break; 106 case '\n': case '\r': /* continued on next line */ 107 if (fgets(line, line_size, fd) != NULL) 108 { 109 ++*linenum; 110 mp = line; 111 } 112 break; 113 case 'x': /* hex value */ 114 ++mp; 115 xbuf_add_char(xbuf, numeric_char(&mp, 16, lmsg_file, *linenum)); 116 break; 117 default: 118 if (*mp >= '0' && *mp <= '7') /* octal value */ 119 xbuf_add_char(xbuf, numeric_char(&mp, 8, lmsg_file, *linenum)); 120 else /* literal char */ 121 xbuf_add_char(xbuf, *mp++); 122 break; 123 } 124 } 125 return mp; 126 } 127 128 /* 129 * Process one line in a lessmsg file. 130 */ 131 static lbool lmsg_line(char *line, size_t line_size, FILE* fd, struct xbuffer *xbuf, constant char *lmsg_file, int *linenum) 132 { 133 lessmsg_id id; 134 char *msg; 135 char *mp; 136 char *sym = skipsp(line); 137 138 if (sym[0] == '\n' || sym[0] == '\r' || sym[0] == '#') 139 return TRUE; /* skip comments and blank lines */ 140 msg = strchr(sym, ' '); 141 if (msg == NULL) 142 { 143 lmsg_error("missing space", lmsg_file, *linenum); 144 return FALSE; 145 } 146 *msg++ = '\0'; /* terminate symbol */ 147 msg = skipsp(msg); 148 id = lmsg_id(sym); 149 if (id == 0) 150 { 151 lmsg_error("unknown lessmsg symbol", lmsg_file, *linenum); 152 return FALSE; 153 } 154 /* Copy the message string into xbuf. */ 155 xbuf_reset(xbuf); 156 for (mp = msg; *mp != '\0' && *mp != '\n' && *mp != '\r'; ) 157 mp = lmsg_char(mp, line, line_size, fd, xbuf, lmsg_file, linenum); 158 /* Store the message string in the table. */ 159 msg = saven(xbuf_char_data(xbuf), xbuf->end); 160 if (id == LM_HELP) 161 { 162 help_data = msg; 163 size_help_data = xbuf->end; 164 } else 165 lmsgs[id] = msg; 166 return TRUE; 167 } 168 169 /* 170 * Initialize the lmsgs array from an lmsg file. 171 */ 172 public void lmsg_init(constant char *lmsg_file) 173 { 174 FILE *fd; 175 char line[512]; 176 struct xbuffer xbuf; 177 int linenum; 178 PARG parg; 179 180 help_data = helpdata; 181 size_help_data = size_helpdata; 182 if (isnullenv(lmsg_file)) 183 return; 184 185 fd = fopen(lmsg_file, "r"); 186 if (fd == NULL) 187 { 188 parg.p_string = lmsg_file; 189 error("Cannot open lessmsg file %s", &parg); 190 return; 191 } 192 xbuf_init(&xbuf); /* for efficiency, init once here and reuse for each line */ 193 linenum = 0; 194 while (fgets(line, sizeof(line), fd) != NULL) 195 { 196 ++linenum; 197 lmsg_line(line, sizeof(line), fd, &xbuf, lmsg_file, &linenum); 198 } 199 xbuf_deinit(&xbuf); 200 fclose(fd); 201 } 202 203 /* 204 * Return the string associated with a numeric lmsg id. 205 */ 206 public constant char * lmsg(lessmsg_id id) 207 { 208 #if 1 209 if (id <= 0 || id >= countof(lmsgs)) 210 { 211 PARG parg; 212 parg.p_int = (int) id; 213 error("invalid lessmsg id %d", &parg); 214 return NULL; 215 } 216 #endif 217 return lmsgs[id]; 218 } 219