%{
/*
 * cstrings.c -- quick-and-dirty internationalization helper
 *
 * by Eric S. Raymond <esr@snark.thyrsus.com>, July 10 1990
 *
 * version 2.0, November 15 1993 -- strict ANSI/POSIX conformance.
 * version 2.3, December 29 2003 -- remplace tempnam(3) with mkstemp(3)
 */
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

typedef int	bool;
#define TRUE	1
#define FALSE	0

static bool	skipline = FALSE, checkmode = FALSE, incomment = FALSE;
static char	*ranges, *template = "G%05d";
static int	minlength = 3;
static FILE	*listfp;

/* this bizarreness avoids dangling-else problems with the macroexpansion */
#undef output
#define output(c)	(checkmode || putc(c,yyout))
#undef ECHO
#define ECHO		(checkmode || fputs(yytext, yyout))

/*
 * The VIS class is intended to include all printables except the string quote.
 * EBCDIC sites can eat flaming death, thank you.  Have a nice day.
 */
%}

%option yylineno

VIS			[!#-~ 	]
QUOTE			\"
%%
^#.*\n			{ECHO;}
\/\*			{ECHO; incomment = TRUE;}
\*\/			{ECHO; incomment = FALSE;}
\\{QUOTE}		{ECHO;}
{QUOTE}{VIS}*{QUOTE}	{
			    if (!inrange(yylineno,ranges) || reject(yytext) || incomment)
				ECHO;
			    else
			    {
				if (checkmode)
				    (void) printf("%d: %s\n", yylineno,yytext);
				else
				{
				    char	label[BUFSIZ];
				    static int	lnum = 1;

				    (void) sprintf(label, template, lnum);
				    (void) fputs(label, yyout);
				    (void) fprintf(listfp,
						   "#define %s\t%s\n",
						   label, yytext);
				    lnum++;
				}
			    }
			}
.			{ECHO;}
%%
yywrap() {}

int inrange(num, cp)
int	num;
char	*cp;
{
#define S_COMMA	    0	/* low end of a range has been gotten */
#define S_NUM	    1	/* comma after a number has been seen */
#define S_DASH	    2	/* dash has been seen */
#define S_EOL	    3	/* end-of-line seen */
#define S_SOL	    4	/* start-of-line state */

    static int	action[4][5]
	    = {{0,1,0,0,0}, {0,0,2,0,0}, {0,3,0,0,4}, {0,5,6,0,0}};

    int	 toktype, state;
    int  lo = 0;
    int  val = 0;
    int  i;
    bool dflt, bang, in;

    if (cp == (char *)NULL)
	return(TRUE);

    dflt = (cp[0] != '!');
    bang = FALSE;
    in = FALSE;

    for (state = S_SOL; state != S_EOL; state = toktype)
    {
	/*
	 * Here's the lexical analyzer
	 */
	while (*cp && isspace(*cp))	/* skip leading whitespace */
	    cp++;

	/* look for a token */
	if (*cp == '\0')
	{
	    toktype = S_EOL;
	}
	else if (*cp == '!')	    /* a complement mark */
	{
	    bang = dflt;
	    cp++;
	}
	else if (*cp == ',')	    /* a comma (ends current range) */
	{
	    toktype = S_COMMA;
	    cp++;
	}
	else if (*cp == '-')	    /* a dash (flags a real range) */
	{
	    toktype = S_DASH;
	    cp++;
	}
	else if (isdigit(*cp))	    /* or a digit sequence */
	{
	    val = atoi(cp);
	    toktype = S_NUM;
	    while (isdigit(*cp))
		cp++;
	}
	else			    /* anything else is an illegal char */
	    return(FALSE);

	/*
	 * Here's the state machine that does the grammar processing
	 */
	switch(action[toktype][state])
	{
	case 1:		    /* number followed by comma */
	    if (num == val)
		in = !bang;
	    bang = FALSE;
	    break;

	case 2:		    /* dash followed by number */
	    if (num >= lo && num <= val)
		in = !bang;
	    break;

	case 3:		    /* number followed by dash */
	    lo = val;
	    break;

	case 4:		    /* start-of-field followed by dash */
	    lo = 1;
	    break;

	case 5:		    /* digit followed by end-of-field */
	    if (num == val)
		in = !bang;
	    break;

	case 6:		    /* dash followed by end-of-field */
	    if (num >= lo)
		in = !bang;
	    break;
	}
    }
    return(in == dflt);
}

int reject(str)
char	*str;
{
    /*
     * Whether to add more filtering options here
     * is an interesting question in design
     * philosophy...
     */
    return(minlength && ((int)strlen(str) - 2 < minlength));
}

void cstrfilt(ifp, ofp)
/* filter yyin to stdout, doing the conversion */
FILE	*ifp, *ofp;
{
    char	buf[BUFSIZ];

    yyin = ifp;
    if (!checkmode)
    {
	if ((listfp = tmpfile()) == (FILE *)NULL)
	{
	    perror("cstrings, opening list tempfile");
	    exit(1);
	} 

	if ((yyout = tmpfile()) == (FILE *)NULL)
	{
	    perror("cstrings, opening source tempfile");
	    exit(1);
	} 
    }

    yylex();

    if (!checkmode)
    {
	/* dump the generated #define list */
	(void) fseek(listfp, 0L, 0);
	while (fgets(buf, BUFSIZ, listfp) != (char *)NULL)
	    (void) fputs(buf, ofp);
	(void) fclose(listfp);

	/* now dump the filtered C text */
	(void) fseek(yyout, 0L, 0);
	while (fgets(buf, BUFSIZ, yyout) != (char *)NULL)
	    (void) fputs(buf, ofp);
	(void) fclose(yyout);
    }
}

main(argc, argv)
int argc;
char *argv[];
{
    extern char	*optarg;	/* set by getopt */
    extern int	optind;		/* set by getopt */
    extern int	yydebug;

    extern FILE *tmpfile();
    int		status;

    while ((status = getopt(argc, argv, "cm:r:t:v")) != EOF)
    {
	switch (status)
	{
	case 'c':
	    checkmode = TRUE;
	    break;

	case 'm':
	    minlength = atoi(optarg);
	    break;

	case 'r':
	    ranges = optarg;
	    break;

	case 't':
	    template = optarg;
	    break;

	default:
	    (void) fprintf(stderr,
		   "cstrings [-c] [-r ranges] [-t template] [-m minlength]\n");
	    exit(0);
	}
    }

    if (optind >= argc)
	cstrfilt(stdin, stdout);
    else
	for (; optind < argc; optind++)
	{
	    char	buf[BUFSIZ], *cp, *tf;
	    FILE	*ifp, *ofp;

	    if ((ifp = fopen(argv[optind], "r")) == (FILE *)NULL)
	    {
		perror("cstrings, opening input file");
		exit(1);
	    }

	    if (checkmode)
		(void) printf("%s:\n", argv[optind]);
	    else
	    {
		if (argv[optind][0] != '/') {
		    if (getcwd(buf, BUFSIZ) != 0)
		    {
			perror("cstrings, getting current directory");
			exit(1);
		    }
		}
		else
		    buf[0] = '\0';
		(void) strcat(buf, argv[optind]);
		if (cp = strrchr(buf, '/'))
		    *cp = '\0';
		strcpy(buf, "cstrXXXXXX");
		if (mkstemp(buf))
		{
		    perror("cstrings, making tempfile");
		    exit(1);
		}

		if ((ofp = fopen(tf, "w")) == (FILE *)NULL)
		{
		    perror("cstrings, making output file");
		    exit(1);
		}
	    }

	    cstrfilt(ifp, ofp);

	    (void) fclose(ifp);

	    if (!checkmode && access(argv[optind], W_OK) == 0)
	    {
		(void) fclose(ofp);
		(void) unlink(argv[optind]);
		if (link(tf, argv[optind]) < 0)	/* that magic moment... */
		{
		    perror("cstrings, linking converted form to original");
		    exit(1);
		}
	    }
	}
}

/*
 The following sets edit modes for GNU EMACS
 Local Variables:
 mode:c
 End:
*/
/* cstrings.l ends here */
