1 /*
2 * Generate C code to initialize and support Omega colors on UNIX.
3 *
4 * Usage:
5 * cpp -DOMEGA_CLRGEN *.[ch] | genclr <c-file> <h-file>
6 * where <c-file> should be compiled and linked with the Omega binary and
7 * <h-file> should be #included in all Omega sources that reference colors.
8 *
9 * Curses color micro-tutorial:
10 * The curses library requires color attributes to be specified as
11 * COLOR_PAIR(<n>) macro calls, where init_pair(<n>, <fore-color>,
12 * <back-color>) has previously been called. <n> must fall in the range
13 * 1..COLOR_PAIRS, where COLOR_PAIRS is a run-time read-only curses variable
14 * typically set to something like 64.
15 *
16 * <c-file> defines function clrgen_init(), which contains the minimal number
17 * of curses init_pair() calls to support the color usages detected by this
18 * program on standard input.
19 *
20 * <h-file> defines preprocessor variables that cause each detected color
21 * usage to expand to an appropriate curses COLOR_PAIR() call.
22 *
23 * This approach to UNIX color support is perhaps overkill, but does have
24 * advantages over these alternative approaches:
25 *
26 * - hard-coded init_pair() calls and color definitions, which would
27 * require manual checking and possibly editing for color reference added
28 * or removed anywhere in the sources;
29 *
30 * - replacement of color references with function calls that lazily call
31 * init_pair() as necessary, which would consume more run-time resources
32 * and behave unpredictably if the number of pairs exceeded COLOR_PAIRS;
33 *
34 * - run-time analysis of color pairs required by e.g. monster list, which
35 * would be a bit more complex to code, consume more run-time resources,
36 * and require significant code changes to move all color references into
37 * static storage.
38 */
39
40 #ifndef OMEGA_CLRGEN /* this file confuses its own scanner */
41
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45
46 /*
47 * Special tag cpp prepends to color symbols
48 */
49 #define PREFIX "OMEGA_CLRGEN"
50
51 /*
52 * Return whether a character could be part of a C identifier
53 */
54 #define ISCID(c) ((c) >= 'A' && (c) <= 'Z' || \
55 (c) >= 'a' && (c) <= 'z' || \
56 (c) >= '0' && (c) <= '9' || \
57 (c) == '_')
58
59 /*
60 * Colors specified in cpp output on standard input
61 */
62 typedef struct {
63 char *ofg, *obg; /* Omega fore/background color */
64 char *cfg, *cbg; /* curses fore/background color */
65 unsigned int boldfg, boldbg; /* fore/background bold flag */
66 unsigned int idx; /* COLOR_PAIR() argument */
67 } ClrPair;
68
69 /*
70 * Omega versus curses color names
71 */
72 typedef struct {
73 char *omega;
74 char *curses;
75 unsigned int bold;
76 } ClrEquiv;
77
78 static ClrEquiv clr_equiv[17] = {
79 { "BLACK", "BLACK", 0 },
80 { "BLUE", "BLUE", 0 },
81 { "GREEN", "GREEN", 0 },
82 { "CYAN", "CYAN", 0 },
83 { "RED", "RED", 0 },
84 { "PURPLE", "MAGENTA", 0 },
85 { "BROWN", "YELLOW", 0 },
86 { "WHITE", "WHITE", 0 },
87 { "GREY", "BLACK", 1 },
88 { "LIGHT_BLUE", "BLUE", 1 },
89 { "LIGHT_GREEN", "GREEN", 1 },
90 { "LIGHT_CYAN", "CYAN", 1 },
91 { "LIGHT_RED", "RED", 1 },
92 { "LIGHT_PURPLE", "MAGENTA", 1 },
93 { "YELLOW", "YELLOW", 1 },
94 { "BRIGHT_WHITE", "WHITE", 1 },
95 { NULL, NULL, 0 } };
96
clr_lookup(char * omega,char ** curses,unsigned int * bold)97 static char *clr_lookup (char *omega, char **curses, unsigned int *bold)
98 {
99 /*
100 * Point CURSES to the curses color corresponding to Omega color OMEGA,
101 * set *BOLD to whether the bold attribute should accompany that curses
102 * color, and return a copy of OMEGA. If OMEGA is unrecognized, return
103 * null.
104 */
105 ClrEquiv *e = clr_equiv;
106 for (; e->omega; e++)
107 if (!strcmp (e->omega, omega)) {
108 *curses = e->curses;
109 *bold = e->bold;
110 return e->omega;
111 }
112 return NULL;
113 }
114
clr_scan(char * p,char ** curses,unsigned int * bold,char ** end)115 static char *clr_scan (char *p, char **curses, unsigned int *bold, char **end)
116 {
117 /*
118 * Return a copy of the Omega color nearest the start of writable buffer
119 * P, point CURSES to the corresponding curses color, and point END just
120 * past the color's location in P.
121 *
122 * If the Omega color is unrecognized, issue an error and exit.
123 */
124 char c, *start, *omega;
125 for (; c = *p; p++) {
126 if (!ISCID (c))
127 continue;
128 for (start = p++; c = *p; p++) {
129 if (ISCID (c))
130 continue;
131 *p = '\0';
132 if (!(omega = clr_lookup (start, curses, bold))) {
133 fprintf (stderr, "unrecognized Omega color \"%s\"\n", start);
134 exit (1);
135 }
136 *p = c;
137 *end = p;
138 return omega;
139 }
140 }
141 return NULL;
142 }
143
opaircmp(const void * pair1,const void * pair2)144 static int opaircmp (const void *pair1, const void *pair2)
145 {
146 /*
147 * qsort comparison function: return less than, equal to, or greater than
148 * 0 according to whether PAIR1 precedes, coincides with, or follows PAIR2
149 * in a sorted list of Omega color pairs.
150 */
151 ClrPair *p1 = (ClrPair *)pair1, *p2 = (ClrPair *)pair2;
152 int diff = strcmp (p1->ofg, p2->ofg);
153 if (diff)
154 return diff;
155 return strcmp (p1->obg, p2->obg);
156 }
157
cpaircmp(const void * pair1,const void * pair2)158 static int cpaircmp (const void *pair1, const void *pair2)
159 {
160 /*
161 * qsort comparison function: return less than, equal to, or greater than
162 * 0 according to whether PAIR1 precedes, coincides with, or follows PAIR2
163 * in a sorted list of curses color pairs.
164 */
165 ClrPair *p1 = *(ClrPair **)pair1, *p2 = *(ClrPair **)pair2;
166 int diff = strcmp (p1->cfg, p2->cfg);
167 if (diff)
168 return diff;
169 return strcmp (p1->cbg, p2->cbg);
170 }
171
emitopen(char * file,char ** argv)172 static FILE *emitopen (char *file, char **argv)
173 {
174 /*
175 * Write to the top of FILE a suitable header based on ARGV, and return a
176 * writable file pointer on FILE. Exit on error.
177 */
178 FILE *fp = fopen (file, "w");
179 if (!fp) {
180 fprintf (stderr, "error opening %s", file);
181 perror ("");
182 exit (1);
183 }
184 fprintf (fp, "\
185 /*\n\
186 * Do not edit this file. It was automatically generated by running:\n\
187 * %s %s %s\n\
188 */\n\
189 \n\
190 ",
191 argv[0], argv[1], argv[2]);
192 return fp;
193 }
194
emitclose(FILE * fp,char * file)195 static void emitclose (FILE *fp, char *file)
196 {
197 /*
198 * Close FP attached to FILE, exiting on error.
199 */
200 if (fclose (fp) == 0)
201 return;
202 fprintf (stderr, "error closing %s", file);
203 perror ("");
204 exit (1);
205 }
206
main(int argc,char ** argv)207 int main (int argc, char **argv)
208 {
209 char line[1024], *p;
210 unsigned int i, j, nopairs = 0, ncpairs, opairslen = 80, one;
211 ClrPair *pair;
212 ClrPair *opairs; /* Omega color pairs */
213 ClrPair **cpairs; /* curses color pairs */
214 char *cfile, *hfile;
215 FILE *fp;
216
217 if (argc != 3) {
218 fprintf (stderr, "usage: %s <c-file> <h-file>\n", argv[0]);
219 exit (1);
220 }
221 cfile = argv[1];
222 hfile = argv[2];
223
224 /*
225 * Accumulate Omega color pairs from standard input into pairs.
226 */
227 opairs = (ClrPair *)malloc (opairslen * sizeof (ClrPair));
228 while (fgets (line, 1024, stdin)) {
229 for (p = line; p = strstr (p, PREFIX);) {
230 p += sizeof (PREFIX) - 1;
231 if (nopairs == opairslen) {
232 opairslen *= 2;
233 opairs = (ClrPair *)realloc (opairs, opairslen *
234 sizeof (ClrPair));
235 }
236 pair = opairs + nopairs++;
237 one = *p++ == '1';
238 pair->ofg = clr_scan (p, &pair->cfg, &pair->boldfg, &p);
239 pair->obg = one ?
240 clr_lookup ("BLACK", &pair->cbg, &pair->boldbg) :
241 clr_scan (p, &pair->cbg, &pair->boldbg, &p);
242 if (pair->boldbg)
243 fprintf (stderr, "warning: \"%s\": bg bold unimplemented\n",
244 pair->obg);
245 }
246 }
247 if (!nopairs) {
248 fputs ("no colors detected in standard input\n", stderr);
249 exit (1);
250 }
251
252 /*
253 * Remove duplicate Omega color pairs.
254 */
255 qsort (opairs, nopairs, sizeof (ClrPair), opaircmp);
256 for (i = 0, j = 1; j < nopairs; j++) {
257 if (opaircmp (opairs + i, opairs + j))
258 opairs[++i] = opairs[j];
259 }
260 nopairs = i + 1;
261
262 /*
263 * Construct a list of unique curses color pairs, and instantiate all
264 * ClrPair.idx fields.
265 */
266 cpairs = (ClrPair **)malloc (nopairs * sizeof (ClrPair *));
267 for (i = 0; i < nopairs; i++)
268 cpairs[i] = opairs + i;
269 qsort (cpairs, nopairs, sizeof (ClrPair *), cpaircmp);
270 cpairs[0]->idx = 1;
271
272 for (i = 0, j = 1; j < nopairs; j++) {
273 if (cpaircmp (cpairs + i, cpairs + j))
274 cpairs[++i] = cpairs[j];
275 cpairs[j]->idx = i + 1;
276 }
277 ncpairs = i + 1;
278
279 /*
280 * Emit .c file.
281 */
282 fp = emitopen (cfile, argv);
283 fprintf (fp, "\
284 #ifdef __FreeBSD__\n\
285 #include <ncurses.h>\n\
286 #else\n\
287 #include <curses.h>\n\
288 #endif\n\
289 #include <stdio.h>\n\
290 #include <stdlib.h>\n\
291 \n\
292 #include \"%s\"\n\
293 \n\
294 void clrgen_init (void)\n\
295 ""{\n\
296 if (%d > COLOR_PAIRS - 1) {\n\
297 endwin();\n\
298 fputs (\"Too many color pairs!\\n\", stderr);\n\
299 exit (1);\n\
300 }\n\
301 ",
302 hfile, ncpairs);
303 for (i = 0; i < ncpairs; i++)
304 fprintf (fp, "\
305 init_pair (%d, COLOR_%s, COLOR_%s);\n\
306 ",
307 cpairs[i]->idx, cpairs[i]->cfg, cpairs[i]->cbg);
308 fputs ("\
309 ""}\n\
310 ",
311 fp);
312 emitclose (fp, cfile);
313
314 /*
315 * Emit .h file.
316 */
317 fp = emitopen (hfile, argv);
318 for (i = 0; i < nopairs; i++) {
319 pair = opairs + i;
320 fprintf (fp, "#define CLR_%s_%s\t%sCOLOR_PAIR(%d)%s\n",
321 pair->ofg, pair->obg,
322 strlen (pair->ofg) + strlen (pair->obg) > 10 ? "" : "\t",
323 pair->idx, pair->boldfg ? "|A_BOLD" : "");
324 }
325 fputs ("\
326 \n\
327 extern void clrgen_init (void);\n\
328 ",
329 fp);
330 emitclose (fp, hfile);
331
332 return 0;
333 }
334
335 #endif /* !OMEGA_CLRGEN */
336