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