omega-rpg/genclr.c

366 lines
9.9 KiB
C

/*
* Generate C code to initialize and support Omega colors on UNIX.
*
* Usage:
* cpp -DOMEGA_CLRGEN *.[ch] | genclr <c-file> <h-file>
* where <c-file> should be compiled and linked with the Omega binary and
* <h-file> should be #included in all Omega sources that reference colors.
*
* Curses color micro-tutorial:
* The curses library requires color attributes to be specified as
* COLOR_PAIR(<n>) macro calls, where init_pair(<n>, <fore-color>,
* <back-color>) has previously been called. <n> must fall in the range
* 1..COLOR_PAIRS, where COLOR_PAIRS is a run-time read-only curses variable
* typically set to something like 64.
*
* <c-file> defines function clrgen_init(), which contains the minimal number
* of curses init_pair() calls to support the color usages detected by this
* program on standard input.
*
* <h-file> defines preprocessor variables that cause each detected color
* usage to expand to an appropriate curses COLOR_PAIR() call.
*
* This approach to UNIX color support is perhaps overkill, but does have
* advantages over these alternative approaches:
*
* - hard-coded init_pair() calls and color definitions, which would
* require manual checking and possibly editing for color reference added
* or removed anywhere in the sources;
*
* - replacement of color references with function calls that lazily call
* init_pair() as necessary, which would consume more run-time resources
* and behave unpredictably if the number of pairs exceeded COLOR_PAIRS;
*
* - run-time analysis of color pairs required by e.g. monster list, which
* would be a bit more complex to code, consume more run-time resources,
* and require significant code changes to move all color references into
* static storage.
*/
#ifndef OMEGA_CLRGEN /* this file confuses its own scanner */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* Special tag cpp prepends to color symbols
*/
#define PREFIX "OMEGA_CLRGEN"
/*
* Return whether a character could be part of a C identifier
*/
#define ISCID(c) \
(((c) >= 'A' && (c) <= 'Z') || \
((c) >= 'a' && (c) <= 'z') || \
((c) >= '0' && (c) <= '9') || \
(c) == '_')
/*
* Colors specified in cpp output on standard input
*/
typedef struct {
char *ofg, *obg; /* Omega fore/background color */
char *cfg, *cbg; /* curses fore/background color */
unsigned int boldfg, boldbg; /* fore/background bold flag */
unsigned int idx; /* COLOR_PAIR() argument */
} ClrPair;
/*
* Omega versus curses color names
*/
typedef struct {
char *omega;
char *curses;
unsigned int bold;
} ClrEquiv;
#ifdef USE_OPCURSES
static ClrEquiv clr_equiv[17] = {
{ "BLACK", "BLACK", 0 },
{ "BLUE", "BLUE", 0 },
{ "GREEN", "GREEN", 0 },
{ "CYAN", "CYAN", 0 },
{ "RED", "RED", 0 },
{ "PURPLE", "MAGENTA", 0 },
{ "BROWN", "YELLOW", 0 },
{ "WHITE", "WHITE", 0 },
{ "GREY", "WHITE", 1 },
{ "LIGHT_BLUE", "BLUE", 1 },
{ "LIGHT_GREEN", "GREEN", 1 },
{ "LIGHT_CYAN", "CYAN", 1 },
{ "LIGHT_RED", "RED", 1 },
{ "LIGHT_PURPLE", "MAGENTA", 1 },
{ "YELLOW", "YELLOW", 1 },
{ "BRIGHT_WHITE", "WHITE", 1 },
{ NULL, NULL, 0 } };
#else
static ClrEquiv clr_equiv[17] = {
{ "BLACK", "BLACK", 0 },
{ "BLUE", "BLUE", 0 },
{ "GREEN", "GREEN", 0 },
{ "CYAN", "CYAN", 0 },
{ "RED", "RED", 0 },
{ "PURPLE", "MAGENTA", 0 },
{ "BROWN", "YELLOW", 0 },
{ "WHITE", "WHITE", 0 },
{ "GREY", "BLACK", 1 },
{ "LIGHT_BLUE", "BLUE", 1 },
{ "LIGHT_GREEN", "GREEN", 1 },
{ "LIGHT_CYAN", "CYAN", 1 },
{ "LIGHT_RED", "RED", 1 },
{ "LIGHT_PURPLE", "MAGENTA", 1 },
{ "YELLOW", "YELLOW", 1 },
{ "BRIGHT_WHITE", "WHITE", 1 },
{ NULL, NULL, 0 } };
#endif
static char *clr_lookup (char *omega, char **curses, unsigned int *bold)
{
/*
* Point CURSES to the curses color corresponding to Omega color OMEGA,
* set *BOLD to whether the bold attribute should accompany that curses
* color, and return a copy of OMEGA. If OMEGA is unrecognized, return
* null.
*/
ClrEquiv *e = clr_equiv;
for (; e->omega; e++)
if (!strcmp (e->omega, omega)) {
*curses = e->curses;
*bold = e->bold;
return e->omega;
}
return NULL;
}
static char *clr_scan (char *p, char **curses, unsigned int *bold, char **end)
{
/*
* Return a copy of the Omega color nearest the start of writable buffer
* P, point CURSES to the corresponding curses color, and point END just
* past the color's location in P.
*
* If the Omega color is unrecognized, issue an error and exit.
*/
char c, *start, *omega;
for (; (c = *p); p++) {
if (!ISCID (c))
continue;
for (start = p++; (c = *p); p++) {
if (ISCID (c))
continue;
*p = '\0';
if (!(omega = clr_lookup (start, curses, bold))) {
fprintf (stderr, "unrecognized Omega color \"%s\"\n", start);
exit (1);
}
*p = c;
*end = p;
return omega;
}
}
return NULL;
}
static int opaircmp (const void *pair1, const void *pair2)
{
/*
* qsort comparison function: return less than, equal to, or greater than
* 0 according to whether PAIR1 precedes, coincides with, or follows PAIR2
* in a sorted list of Omega color pairs.
*/
ClrPair *p1 = (ClrPair *)pair1, *p2 = (ClrPair *)pair2;
int diff = strcmp (p1->ofg, p2->ofg);
if (diff)
return diff;
return strcmp (p1->obg, p2->obg);
}
static int cpaircmp (const void *pair1, const void *pair2)
{
/*
* qsort comparison function: return less than, equal to, or greater than
* 0 according to whether PAIR1 precedes, coincides with, or follows PAIR2
* in a sorted list of curses color pairs.
*/
ClrPair *p1 = *(ClrPair **)pair1, *p2 = *(ClrPair **)pair2;
int diff = strcmp (p1->cfg, p2->cfg);
if (diff)
return diff;
return strcmp (p1->cbg, p2->cbg);
}
static FILE *emitopen (char *file, char **argv)
{
/*
* Write to the top of FILE a suitable header based on ARGV, and return a
* writable file pointer on FILE. Exit on error.
*/
FILE *fp = fopen (file, "w");
if (!fp) {
fprintf (stderr, "error opening %s", file);
perror ("");
exit (1);
}
fprintf (fp, "\
/*\n\
* Do not edit this file. It was automatically generated by running:\n\
* %s %s %s\n\
*/\n\
\n\
",
argv[0], argv[1], argv[2]);
return fp;
}
static void emitclose (FILE *fp, char *file)
{
/*
* Close FP attached to FILE, exiting on error.
*/
if (fclose (fp) == 0)
return;
fprintf (stderr, "error closing %s", file);
perror ("");
exit (1);
}
int main (int argc, char **argv)
{
char line[1024], *p;
unsigned int i, j, nopairs = 0, ncpairs, opairslen = 80, one;
ClrPair *pair;
ClrPair *opairs; /* Omega color pairs */
ClrPair **cpairs; /* curses color pairs */
char *cfile, *hfile;
FILE *fp;
if (argc != 3) {
fprintf (stderr, "usage: %s <c-file> <h-file>\n", argv[0]);
exit (1);
}
cfile = argv[1];
hfile = argv[2];
/*
* Accumulate Omega color pairs from standard input into pairs.
*/
opairs = (ClrPair *)malloc (opairslen * sizeof (ClrPair));
while (fgets (line, 1024, stdin)) {
for (p = line; (p = strstr (p, PREFIX));) {
p += sizeof (PREFIX) - 1;
if (nopairs == opairslen) {
opairslen *= 2;
opairs = (ClrPair *)realloc (opairs, opairslen *
sizeof (ClrPair));
}
pair = opairs + nopairs++;
one = *p++ == '1';
pair->ofg = clr_scan (p, &pair->cfg, &pair->boldfg, &p);
pair->obg = one ?
clr_lookup ("BLACK", &pair->cbg, &pair->boldbg) :
clr_scan (p, &pair->cbg, &pair->boldbg, &p);
if (pair->boldbg)
fprintf (stderr, "warning: \"%s\": bg bold unimplemented\n",
pair->obg);
}
}
if (!nopairs) {
fputs ("no colors detected in standard input\n", stderr);
exit (1);
}
/*
* Remove duplicate Omega color pairs.
*/
qsort (opairs, nopairs, sizeof (ClrPair), opaircmp);
for (i = 0, j = 1; j < nopairs; j++) {
if (opaircmp (opairs + i, opairs + j))
opairs[++i] = opairs[j];
}
nopairs = i + 1;
/*
* Construct a list of unique curses color pairs, and instantiate all
* ClrPair.idx fields.
*/
cpairs = (ClrPair **)malloc (nopairs * sizeof (ClrPair *));
for (i = 0; i < nopairs; i++)
cpairs[i] = opairs + i;
qsort (cpairs, nopairs, sizeof (ClrPair *), cpaircmp);
cpairs[0]->idx = 1;
for (i = 0, j = 1; j < nopairs; j++) {
if (cpaircmp (cpairs + i, cpairs + j))
cpairs[++i] = cpairs[j];
cpairs[j]->idx = i + 1;
}
ncpairs = i + 1;
/*
* Emit .c file.
*/
fp = emitopen (cfile, argv);
#ifdef USE_OPCURSES
fprintf (fp, "\
#include \"../opcurses/curses.h\"\n");
#else
fprintf (fp, "\
#include <curses.h>\n");
#endif
fprintf (fp, "\
#include <stdio.h>\n\
#include <stdlib.h>\n\
\
#include \"%s\"\n\
\
void clrgen_init (void)\n\
""{\n\
if (!has_colors()) {\n\
fprintf(stderr,\"Omega currently requires ncurses or op-curses color support.\\n\");\n\
return;\n\
} else\n\
if (%d > COLOR_PAIRS - 1) {\n\
endwin();\n\
fprintf (stderr,\"Too few available color pairs (found %%d, need %d)!\\n\", COLOR_PAIRS);\n\
exit (1);\n\
}\n\
",
hfile, ncpairs, ncpairs);
for (i = 0; i < ncpairs; i++)
fprintf (fp, "\
init_pair (%d, COLOR_%s, COLOR_%s);\n\
",
cpairs[i]->idx, cpairs[i]->cfg, cpairs[i]->cbg);
fputs ("\
""}\n\
",
fp);
emitclose (fp, cfile);
/*
* Emit .h file.
*/
fp = emitopen (hfile, argv);
for (i = 0; i < nopairs; i++) {
pair = opairs + i;
fprintf (fp, "#define CLR_%s_%s\t%sCOLOR_PAIR(%d)%s\n",
pair->ofg, pair->obg,
strlen (pair->ofg) + strlen (pair->obg) > 10 ? "" : "\t",
pair->idx, pair->boldfg ? "|A_BOLD" : "");
}
fputs ("\
\n\
extern void clrgen_init (void);\n\
",
fp);
emitclose (fp, hfile);
return 0;
}
#endif /* !OMEGA_CLRGEN */