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 */
lmsg_error(constant char * err,constant char * lmsg_file,int linenum)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 */
lmsg_id(constant char * sym)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 */
numeric_char(char ** pp,int radix,constant char * lmsg_file,int linenum)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 */
lmsg_char(char * mp,char * line,size_t line_size,FILE * fd,struct xbuffer * xbuf,constant char * lmsg_file,int * linenum)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 */
lmsg_line(char * line,size_t line_size,FILE * fd,struct xbuffer * xbuf,constant char * lmsg_file,int * linenum)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 */
lmsg_init(constant char * lmsg_file)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 */
lmsg(lessmsg_id id)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