/*
 * Maketool - GTK-based front end for gmake
 * Copyright (c) 1999-2003 Greg Banks
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "filter.h"
#include "util.h"
#if HAVE_REGCOMP
#include <regex.h>	/* POSIX regular expression fns */

CVSID("$Id: filter.c,v 1.49 2003/10/26 09:05:14 gnb Exp $");

typedef struct
{
    char *name;
    GList *filters; 	/* list of Filter's */
} FilterSet;

typedef struct
{
    int nmatches;
    int ntries;
} FilterStats;

typedef struct
{
    char *instate;
    char *outstate;
    regex_t regexp;
    char *file_str;
    char *line_str;
    char *col_str;
    char *summary_str;
    FilterCode code;
    char *comment;
    FilterSet *set;
    FilterStats stats;
} Filter;

static GList *filters;
static GList *filters_by_lead[256]; 	/* organised by leading character */
static GList *filter_sets;  /* in order encountered */
static FilterSet *curr_filter_set;
const char *filter_state = "";
extern const char *argv0;
static FilterStats total_stats;

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

/*
 * Calculate and display the set of possible characters
 * with which lines matching this regexp could start.
 * Assumes the regexp is well formed, does little syntax
 * checking.
 */
static void
filter_calc_leads(const char *regexp, unsigned char suitable[256])
{
    const char *r = regexp;
    int i;
#if DEBUG > 5
#define suits(i, b, c) \
    { \
    	int _i = (i); \
	int _b = (b); \
	fprintf(stderr, "filter_calc_leads: suitable[%d] = %d (%s)\n", _i, _b, (c)); \
	suitable[_i] = _b; \
    }
#define suitall(b, c) \
    { \
	int _b = (b); \
	fprintf(stderr, "filter_calc_leads: suitable[*] = %d (%s)\n", _b, (c)); \
    	memset(suitable, _b, 256); \
    }
#define dmsg1(fmt, a1) \
    fprintf(stderr, "filter_calc_leads: "fmt"\n", (a1))
#else
#define suits(i, b, c) \
    	suitable[(i)] = (b);
#define suitall(b, c) \
    	memset(suitable, (b), 256);
#define dmsg1(fmt, a1)
#endif

    dmsg1("%s", "");
    dmsg1("starting regexp \"%s\"", regexp);
    dmsg1("%s", "");

    if (*r != '^')
    {
	suitall(TRUE, "not anchored");
	return;
    }
    r++;	/* skip leading '^' */
    suitall(FALSE, "initial values");

    if (*r == '\\')
    {
    	suits((int)r[1], TRUE, "escaped char must be a literal");
    }    
    else if (*r == '$' && r[1] == '\0')
    {
    	suits(0, TRUE, "only an empty string matches");
    }
    else
    {
    	for (;;)
	{
    	    gboolean ingroup = FALSE;
	    int nbranches = 0;
	    gboolean negbracket = FALSE;
	    gboolean nullbranch = FALSE;

	    if (*r == '(')
	    {
		dmsg1("starting group \"%s\"", r);
		ingroup = TRUE;
    		r++;   /* skip grouping operator */
	    }

    	    for (;;)
	    {    	
		if (*r == '[')
		{
		    /* handle bracket expressions [xyz] [^xyz] [x-z] [^x-z] */
    		    const char *brstart;
		    gboolean b = TRUE;

		    dmsg1("bracket expression \"%s\"", r);
		    r++;
		    if (*r == '^')
		    {
			suitall(TRUE, "negative bracket expression");
			b = FALSE;
			r++;
			negbracket = TRUE;
		    }
    		    brstart = r;
    		    for ( ; *r && *r != ']' ; r++)
		    {
			if (*r == '-' && r != brstart && r[1] != ']')
			{
	    		    for (i = r[-1] ; i <= r[1] ; i++)
				suits(i, b, "bracket expression range");
			}
			else
			    suits((int)*r, b, "bracket expression atom");
		    }
		}
		else if (ingroup && *r == '|')
		{
		    dmsg1("null branch \"%s\"", r);
		    nullbranch = TRUE;
		}
		else if (*r == '.')
		{
		    suitall(TRUE, "dot");
    	    	}
		else if (!ingroup && *r == '$')
		{
		    dmsg1("anchored at end \"%s\"", r);
		    break;
    	    	}
		else
		{
    		    suits((int)*r, TRUE, "literal");
    	    	}

		nbranches++;
		if (ingroup)
		{
	    	    /* advance to next branch or end */
		    for ( ; *r && *r != '|' && *r != ')' ; r++)
			;
		    if (*r == '|')
		    {
			r++;
			dmsg1("next branch \"%s\"", r);
			continue;
		    }
		    dmsg1("group ends before \"%s\"", r+1);
		}
		r++;
		break;
	    }
	    /*
	     * This code doesn't handle the case ^(foo|[^f]) because a negative
	     * bracket expression in a branch incorrectly overwrites all the other
	     * branches' results.  Happily we don't have any such expressions to
	     * deal with.  But just in case, assert it didn't happen.
	     */
	    assert(!ingroup || nbranches == 1 || !negbracket);
	    
	    /*
	     * Previous expression can occur zero times, so the first
	     * character might match the next expression.  Keep going.
	     * Example: "^[ \t]*(|[^ \t:#]+/)[-/a-z0-9]+..."
	     *            ^^^^^ ^^
	     * The nullbranch case works in GNU libc but is not portable
	     * (in particular FreeBSD regcomp() is known to consider it
	     * an error), so assert that it hasn't happened.
	     */
	    assert(!nullbranch);
	    if (*r == '*')
	    {
		r++;
		dmsg1("expression may be null, continuing with \"%s\"", r);
		continue;
	    }
	    break;
    	}
    }
    
#if DEBUG
    fprintf(stderr, "filter_calc_leads: [%s] -> \n\t\t\"", regexp);
    for (i = 0 ; i < 256 ; i++)
    {
    	if (suitable[i])
	{
	    if (isprint(i))
	    	fputc(i, stderr);
	    else
	    	fprintf(stderr, "\\%03o", i);
	}
    }
    fprintf(stderr, "\"\n");
#endif
}

static Filter *
filter_add(
    const char *state,
    const char *regexp,
    FilterCode code,
    const char *file_str,
    const char *line_str,
    const char *col_str,
    const char *summary_str,
    const char *comment)
{
    Filter *f = g_new(Filter, 1);
    guint err;
    int i;
    unsigned char leads[256];
    
    if ((err = regcomp(&f->regexp, regexp, REG_EXTENDED)) != 0)
    {
        char errbuf[1024];
    	regerror(err, &f->regexp, errbuf, sizeof(errbuf));
	/* TODO: decent error reporting mechanism */
	fprintf(stderr, _("%s: error in regular expression \"%s\": %s\n"),
		argv0, regexp, errbuf);
	regfree(&f->regexp);
	g_free(f);
	return 0;
    }
    f->instate = g_strdup(state);
    if ((f->outstate = strchr(f->instate, ':')) != 0)
    {
    	*f->outstate++ = '\0';
	f->outstate = g_strdup(f->outstate);
    }
    else
    	f->outstate = g_strdup("");
    f->code = code;
    f->file_str = g_strdup(file_str);
    f->line_str = g_strdup(line_str);
    f->col_str = g_strdup(col_str);
    f->summary_str = g_strdup(summary_str);
    f->comment = g_strdup(comment);
    memset(&f->stats, 0, sizeof(f->stats));

    if ((f->set = curr_filter_set) != 0)
    	f->set->filters = g_list_append(f->set->filters, f);
    
    filters = g_list_append(filters, f);

    filter_calc_leads(regexp, leads);
    for (i = 0 ; i < 256 ; i++)
    {
    	if (leads[i])
	    filters_by_lead[i] = g_list_append(filters_by_lead[i], f);
    }

    return f;
}    

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
filter_set_start(const char *name)
{
    GList *iter;
    FilterSet *fs;

    /* Try to find an existing set of the same name */    
    for (iter = filter_sets ; iter != 0 ; iter = iter->next)
    {
    	fs = (FilterSet *)iter->data;

    	if (!strcmp(name, fs->name))
	{
	    curr_filter_set = fs;
	    return;
	}
    }
    
    fs = g_new(FilterSet, 1);
    fs->name = g_strdup(name);
    fs->filters = 0;
    
    filter_sets = g_list_append(filter_sets, fs);
    curr_filter_set = fs;
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

void
filter_load(void)
{
    /*
     * The filters in the "Generic UNIX C compiler" and "GNU cross-compilers"
     * sets don't detect errors, they just provide summaries of compile lines.
     * They are interspersed for historical reasons, it's not yet clear whether
     * they need to be.
     *
     * Sadly, <> don't do quite what I expected in regexps, so they've been
     * replaced with the horror ([ \t]|[ \t].*[ \t]) which matches a string of
     * 1 or more chars starting and ending with whitespace.
     *
     * The order in which filters appear here controls the order in which they
     * are evaluated when parsing logs.  Two factors control the order:
     * 1.  More commonly matched filters should be earlier, to reduce the
     *     number of regexec()s needed to parse logs.
     * 2.  Potentially a line can match two or more filters, so the "right"
     *     one needs to be earlier.
     */
    filter_set_start(_("Generic UNIX C compiler"));
    filter_add(
    	"",				/* state */
	"^[ \t]*(/[^ \t:#]+/)*(cc|c89|gcc|CC|c\\+\\+|g\\+\\+)([ \t]|[ \t].*[ \t])-c([ \t]|[ \t].*[ \t])([^ \t]*\\.)(c|C|cc|c\\+\\+|cpp)", /* regexp */
	FR_INFORMATION,			/* code */
	"\\5\\6",			/* file */
	"",				/* line */
	"",				/* col */
	"Compiling \\5\\6",		/* summary */
    	"C/C++ compile line");		/* comment */

    filter_set_start(_("GNU cross-compilers"));
    filter_add(
    	"",				/* state */
	"^[ \t]*(/[^ \t:#]+/)*[-/a-z0-9]+-(cc|gcc|c\\+\\+|g\\+\\+).*[ \t]-c([ \t]|[ \t].*[ \t])([^ \t]+\\.)(c|C|cc|c\\+\\+|cpp)", /* regexp */
	FR_INFORMATION,			/* code */
	"\\4\\5",			/* file */
	"",				/* line */
	"",				/* col */
	"Cross-compiling \\4\\5",   	/* summary */
    	"GNU C/C++ cross-compile line"); /* comment */

    filter_set_start(_("Linux kernel build"));
    filter_add(
    	"",				/* state */
	"^(/[^ \t:#]+/)+scripts/mkdep", /* regexp */
	FR_INFORMATION,			/* code */
	"",			    	/* file */
	"",				/* line */
	"",				/* col */
	"Building dependencies",   	/* summary */
    	"Linux mkdep line"); 	    	/* comment */

    filter_set_start(_("GNU make directory messages"));

    filter_add(
    	"",				/* state */
	"^(gmake|make|smake|pmake)\\[([0-9]+)\\]: Entering directory `([^']+)'", /* regexp */
	FR_PUSHDIR,			/* code */
	"\\3",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"",		    	    	/* summary */
    	"make recursion - push");	/* comment */

    filter_add(
    	"",				/* state */
	"^(gmake|make|smake|pmake)\\[([0-9]+)\\]: Leaving directory `([^']+)'", /* regexp */
	FR_POPDIR,			/* code */
	"\\3",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"",				/* summary */
    	"make recursion - pop");	/* comment */

    filter_set_start(_("GNU Compiler Collection"));

    filter_add(
    	"",				/* state */
	"^[^:]+:[0-9]+: \\(Each undeclared identifier is reported only once", /* regexp */
	FR_INFORMATION,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"",				/* summary */
    	"gcc spurious message #1");	/* comment */

    filter_add(
    	"",				/* state */
	"^[^:]+:[0-9]+: for each function it appears in.\\)", /* regexp */
	FR_INFORMATION,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"",				/* summary */
    	"gcc spurious message #2");	/* comment */
	
    filter_add(
    	"",				/* state */
	"^[^:]+: In function",	    	/* regexp */
	FR_INFORMATION,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"",				/* summary */
    	"gcc spurious message #3");	/* comment */
	
    filter_add(
    	"",				/* state */
	"^([^: \t]+):([0-9]+):([0-9]+): [wW]arning:(.*)$",	/* regexp */
	FR_WARNING,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"\\3",				/* col */
	"\\4",				/* summary */
    	"new gcc warnings");		/* comment */
	
    filter_add(
    	"",				/* state */
	"^\\{standard input\\}:([0-9]+): [wW]arning:(.*)$",	/* regexp */
	FR_WARNING,			/* code */
	"",				/* file */
	"\\1",				/* line */
	"",				/* col */
	"\\2",				/* summary */
    	"new gas warnings");		/* comment */
	
    filter_add(
    	"",				/* state */
	"^([^: \t]+):([0-9]+): [wW]arning:(.*)$",	/* regexp */
	FR_WARNING,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"gcc warnings");		/* comment */
	
    filter_add(
    	"",				/* state */
	"^([^: \t]+):([0-9]+):([0-9]+):(.*)$",	/* regexp */
	FR_ERROR,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"\\3",				/* col */
	"\\4",				/* summary */
    	"new gcc errors");		/* comment */

    filter_add(
    	"",				/* state */
	"^([^: \t]+):([0-9]+):(.*)$",	/* regexp */
	FR_ERROR,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"gcc errors");			/* comment */

    filter_add(
    	"",				/* state */
	"^In file included from ([^: \t]+):([0-9]+)[,:]$",	/* regexp */
	FR_INFORMATION,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"Included from \\1:\\2",	/* summary */
    	"gcc inclusion trace 1");	/* comment */
    filter_add(
    	"",				/* state */
	"^[ \t]+from +([^: \t]+):([0-9]+)[,:]$",	/* regexp */
	FR_INFORMATION,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"Included from \\1:\\2",	/* summary */
    	"gcc inclusion trace 2");	/* comment */

    filter_set_start(_("Bison & Flex"));

    filter_add(
    	"",				/* state */
	"^\\(\"([^\"]+)\", line ([0-9]+)\\) [Ee]rror: (.*)$",	/* regexp */
	FR_ERROR,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"bison errors");		/* comment */

    /* this regexp is triggered by some MIPSpro messages as well as flex */
    filter_add(
    	"",				/* state */
	"^\"([^\"]*)\", line ([0-9]+): [Ww]arning: (.*)$",	/* regexp */
	FR_WARNING,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"flex warnings");	    	/* comment */

    filter_add(
    	"",				/* state */
	"^\"([^\"]*)\", line ([0-9]+): (.*)$",	/* regexp */
	FR_ERROR,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"flex errors");	    		/* comment */

#if ENABLE_FILTER_HPUX
    filter_set_start(_("HP-UX compilers"));

    filter_add(
    	"",				/* state */
	"(CC|cpp): \"([^\"]*)\", line ([0-9]+): error(.*)$", /* regexp */
	FR_ERROR,			/* code */
	"\\2",				/* file */
	"\\3",				/* line */
	"",				/* col */
	"\\4",				/* summary */
    	"HP-UX old CC/cpp error");	/* comment */
	
    filter_add(
    	"",				/* state */
	"(CC|cpp): \"([^\"]*)\", line ([0-9]+): warning(.*)$", /* regexp */
	FR_WARNING,			/* code */
	"\\2",				/* file */
	"\\3",				/* line */
	"",				/* col */
	"\\4",				/* summary */
    	"HP-UX old CC/cpp warning");	/* comment */
#endif /* ENABLE_FILTER_HPUX */

#if ENABLE_FILTER_MIPSPRO
    filter_set_start(_("IRIX MIPSpro compilers"));
    
    /*
     * IRIX smake in parallel mode emits lines like these before every
     * line of output, but because they list only the target and not the
     * source or directory they're nearly useless to us.
     */
    filter_add(
    	"",			    	/* state */
	"^--- [^ \t:]+ ---$",	    	/* regexp */
	FR_INFORMATION,	    	    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"",	    	    	    	/* summary */
    	"smake target name");   	/* comment */

    /* old style MIPSpro errors and warnings */
    filter_add(
    	":mipspro1",			/* state */
	"^cc-[0-9]+ (cc|CC): ERROR File = ([^ \t,]+), Line = ([0-9]+)", /* regexp */
	FR_PENDING|FR_ERROR,	    	/* code */
	"\\2",				/* file */
	"\\3",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro cc/CC error");   	/* comment */
	
    filter_add(
    	":mipspro1",			/* state */
	"^cc-[0-9]+ (cc|CC): WARNING File = ([^ \t,]+), Line = ([0-9]+)", /* regexp */
	FR_PENDING|FR_WARNING,	    	/* code */
	"\\2",				/* file */
	"\\3",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro cc/CC warning");   	/* comment */

    filter_add(
    	":mipspro1",			/* state */
	"^cc-[0-9]+ (cc|CC): REMARK File = ([^ \t,]+), Line = ([0-9]+)", /* regexp */
	FR_PENDING|FR_WARNING,	    	/* code */
	"\\2",				/* file */
	"\\3",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro cc/CC remark");   	/* comment */

    filter_add(
    	"mipspro1:mipspro2",		/* state */
	"^[ \t]+(.*)$",    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"\\1",	    	    	    	/* summary */
    	"MIPSpro multiline message 1"); /* comment */

    filter_add(
    	"mipspro2:mipspro2a",		/* state */
	"^[ \t]+(.*)$",    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"\\1",	    	    	    	/* summary */
    	"MIPSpro multiline message 2"); /* comment */

    filter_add(
    	"mipspro2a:mipspro3",		/* state */
	"^$",    	    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro multiline message 2"); /* comment */

    filter_add(
    	"mipspro2:mipspro3",		/* state */
	"^$",    	    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro multiline message 2"); /* comment */

    filter_add(
    	"mipspro3:",		    	/* state */
	"^$",    	    	    	/* regexp */
	FR_DONE,    	    	    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro multiline message 3a"); /* comment */

    filter_add(
    	"mipspro3:mipspro4",		/* state */
	"^.*$",    	    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro multiline message 3"); /* comment */

    filter_add(
    	"mipspro4:mipspro5",		/* state */
	"^[ \t]+\\^$",	    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro multiline message 4"); /* comment */

    filter_add(
    	"mipspro5:",		    	/* state */
	"^$",    	    	    	/* regexp */
	FR_DONE,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	0,	    	    	    	/* summary */
    	"MIPSpro multiline message 5"); /* comment */

    /* IRIX linker errors */
    filter_add(
    	"",				/* state */
	"^(ld|ld32|ld64): ERROR[ \t]+[0-9]+[ \t]*: (.*)$", /* regexp */
	FR_ERROR,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"\\2",				/* summary */
    	"MIPSpro ld error");	    	/* comment */
	
    /* new style MIPSpro errors and warnings */
    filter_add(
    	":Nmipspro1",			/* state */
	"^cfe: Error: ([^ \t,]+), line ([0-9]+): (.*)$", /* regexp */
	FR_PENDING|FR_ERROR,	    	/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",	    	    	    	/* summary */
    	"new MIPSpro cc/CC error");   	/* comment */
	
    filter_add(
    	":Nmipspro1",			/* state */
	"^cfe: Warning [0-9]+: ([^ \t,]+), line ([0-9]+): (.*)$", /* regexp */
	FR_PENDING|FR_WARNING,	    	/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",	    	    	    	/* summary */
    	"new MIPSpro cc/CC warning");	/* comment */

    filter_add(
    	"Nmipspro1:Nmipspro2",		/* state */
	"^ (.*)$",    	    	    	/* regexp */
	FR_PENDING|FR_UNDEFINED,    	/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"",	    	    	    	/* summary */
    	"new MIPSpro multiline message 1"); /* comment */

    filter_add(
    	"Nmipspro2:",		    	/* state */
	"^ [- \t]*\\^$",    	    	/* regexp */
	FR_DONE,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"",	    	    	    	/* summary */
    	"MIPSpro multiline message 5"); /* comment */

    /* new style assembler and pre-processor errors */
    filter_add(
    	"",			    	/* state */
	"^cfe: Error: ([^ \t,]+):[ \t]*([0-9]+): (.*)$", /* regexp */
	FR_ERROR,	    	    	/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",	    	    	    	/* summary */
    	"new MIPSpro cpp error");	/* comment */

    filter_add(
    	"",			    	/* state */
	"^cfe: Warning [0-9]+: ([^ \t,]+):[ \t]*([0-9]+): (.*)$", /* regexp */
	FR_WARNING,	    	    	/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",	    	    	    	/* summary */
    	"new MIPSpro cpp warning");	/* comment */

    filter_add(
    	"",			    	/* state */
	"^(as|as1): Warning: ([^ \t,]+), line ([0-9]+): (.*)$", /* regexp */
	FR_WARNING,	    	    	/* code */
	"\\2",				/* file */
	"\\3",				/* line */
	"",				/* col */
	"\\4",	    	    	    	/* summary */
    	"new MIPSpro as warning");	/* comment */
#endif /* ENABLE_FILTER_MIPSPRO */

#if ENABLE_FILTER_SUN
    filter_set_start(_("Solaris compilers (unimplemented)");
    /*TODO: Solaris compilers*/
#endif /* ENABLE_FILTER_SUN */

    filter_set_start(_("Oracle Pro/C"));

    filter_add(
    	"",				/* state */
	"(Semantic|Syntax|Parser) error at line ([0-9]+), column ([0-9]+), file[ \t]*([^ \t]*):(.*)$", /* regexp */
	FR_ERROR,			/* code */
	"\\4",				/* file */
	"\\2",				/* line */
	"\\3",				/* col */
	"\\1 error: \\5",		/* summary */
    	"Oracle Pro/C error");		/* comment */
	
    filter_set_start(_("Generic UNIX C compiler"));
    filter_add(
    	"",				/* state */
	"^[ \t]*(/[^ \t:#]+/)*(cc|c89|gcc|CC|c\\+\\+|g\\+\\+|ld).*[ \t]+-o[ \t]+([^ \t]+)", /* regexp */
	FR_INFORMATION,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"Linking \\3",			/* summary */
    	"C/C++ link line");		/* comment */

    filter_set_start(_("GNU cross-compilers"));
    filter_add(
    	"",				/* state */
	"^[ \t]*(/[^ \t:#]+/)*[-/a-z0-9]+-(cc|gcc|c\\+\\+|g\\+\\+|ld).*[ \t]+-o[ \t]+([^ \t]+)", /* regexp */
	FR_INFORMATION,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"Cross-linking \\3",		/* summary */
    	"GNU C/C++ cross-link line");	/* comment */

    filter_set_start(_("Generic UNIX C compiler"));
    filter_add(
    	"",				/* state */
	"^[ \t]*(/[^ \t:#]+/)*ar[ \t][ \t]*[rc][a-z]*[ \t][ \t]*(lib[^ \t]*.a)", /* regexp */
	FR_INFORMATION,			/* code */
	"",				/* file */
	"",				/* line */
	"",				/* col */
	"Building library \\2",		/* summary */
    	"Archive library link line");	/* comment */
    /* TODO: support for libtool */	

    filter_set_start(_("GNU autoconf messages"));
    filter_add(
    	"",				/* state */
	"^creating ([^ \t]+)$",	    	/* regexp */
	FR_INFORMATION,			/* code */
	"\\1",				/* file */
	"",				/* line */
	"",				/* col */
	0,		    	    	/* summary */
    	"configure script output file 1"); /* comment */
    filter_add(
    	"",				/* state */
	"^([^ \t]+) is unchanged$",  	/* regexp */
	FR_INFORMATION,			/* code */
	"\\1",				/* file */
	"",				/* line */
	"",				/* col */
	0,		    	    	/* summary */
    	"configure script output file 2"); /* comment */

    filter_set_start(_("Sun Java"));
    filter_add(
   	"",				/* state */
	"^javac[ \t].*[ \t]([A-Za-z_*][A-Za-z0-3_*]*).java", /* regexp */
	FR_INFORMATION,			/* code */
	"\\1.java",			/* file */
	"",				/* line */
	"",				/* col */
	"Compiling \\1.java",		/* summary */
    	"Java compile line");		/* comment */


#if ENABLE_FILTER_MWOS
    /*
     * Support for errors generated by cross-compiling
     * for Microware OS-9 using Microware's xcc compiler.
     */
    filter_set_start(_("Microware OS-9 cross-compilers"));

    filter_add(
    	"",				/* state */
	"^\"([^\" \t]+)\", line ([0-9]+): error:(.*)$",	/* regexp */
	FR_ERROR,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"MWOS xcc errors"); 	    	/* comment */
	
    filter_add(
    	"",				/* state */
	"^\"([^\" \t]+)\", line ([0-9]+): warning:(.*)$",	/* regexp */
	FR_WARNING,			/* code */
	"\\1",				/* file */
	"\\2",				/* line */
	"",				/* col */
	"\\3",				/* summary */
    	"MWOS xcc warnings");	    	/* comment */
    /*
     * Support for summarising Microware xcc compile lines.
     * Note that xcc does not use the POSIX standard compile options,
     * using e.g. -v=DIR instead of -IDIR.  In particular,
     * POSIX' -c is xcc' -r.
     */
    filter_add(
    	"",				/* state */
	"^xcc.*[ \t]-r[ \t].*[ \t]([^ \t]*\\.)(c)", /* regexp */
	FR_INFORMATION,			/* code */
	"\\1\\2",			/* file */
	"",				/* line */
	"",				/* col */
	"Compiling \\1\\2",		/* summary */
    	"xcc C compile line");		/* comment */
#endif /* ENABLE_FILTER_MWOS */

}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

void
filter_init(void)
{
    filter_state = "";
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

static void
filter_replace_matches(
    const char *in,
    estring *out,
    const char *line,
    regmatch_t *matches)
{
    estring_truncate(out);
    for ( ; *in ; in++)
    {
    	if (*in == '\\' && in[1] >= '1' && in[1] <= '9')
	{
	    int n = (in[1] - '0');
	    if (matches[n].rm_so >= 0)
	    {
	    	int len = matches[n].rm_eo - matches[n].rm_so;
		estring_append_chars(out, line + matches[n].rm_so, len);
	    }
	    in++;
	}
	else
	    estring_append_char(out, *in);
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
#define NUM_MATCHES 9

#define safe_atoi(s)	((s) == 0 ? 0 : atoi(s))

static gboolean
filter_apply_one(
    Filter *f,
    const char *line,
    FilterResult *result)
{
    regmatch_t matches[NUM_MATCHES];
    static estring buf = ESTRING_STATIC_INIT;
    int i;
    
    if (strcmp(filter_state, f->instate))
	return FALSE;

    f->stats.ntries++;
    total_stats.ntries++;

    if (regexec(&f->regexp, line, NUM_MATCHES, matches, 0))
	return FALSE;

    f->stats.nmatches++;
    total_stats.nmatches++;

    filter_state = f->outstate;

    /*
     * Set (parts of) the result using the matched subexpressions.
     */
    filter_replace_matches(f->line_str, &buf, line, matches);
    if ((i = safe_atoi(buf.data)) > 0)
	result->line = i;

    filter_replace_matches(f->col_str, &buf, line, matches);
    if ((i = safe_atoi(buf.data)) > 0)
	result->column = i;

    if (f->summary_str != 0)
    {
    	if (*f->summary_str == '\0')
	{
	    result->summary = g_strdup("");
    	}
	else
	{
	    filter_replace_matches(f->summary_str, &buf, line, matches);
	    if (buf.data != 0 && *buf.data != '\0')
	    {
	    	if (result->summary != 0 &&
		    ((f->code & FR_PENDING) || f->code == FR_DONE))
		{
		    /* append to the summary */
		    char *sum = g_strconcat(result->summary, " ", buf.data, 0);
#if DEBUG
	    	    fprintf(stderr, "filter_apply_one: appending summary \"%s\" to \"%s\"\n",
			result->summary, sum);
#endif
		    g_free(result->summary);
		    result->summary = sum;
		}
		else
		{
		    /* replace or start the summary */
		    if (result->summary != 0)
		    {
#if DEBUG
	    		fprintf(stderr, "filter_apply_one: replacing summary \"%s\" with \"%s\"\n",
			    result->summary, buf.data);
#endif
			g_free(result->summary);
		    }
		    result->summary = g_strdup(buf.data);
		}
	    }
	}
    }

    filter_replace_matches(f->file_str, &buf, line, matches);
    if (buf.data != 0 && *buf.data != '\0')
	result->file = g_strdup(buf.data);

    if (f->code == FR_DONE)
    	result->code &= ~FR_PENDING;
    else if ((f->code & ~FR_PENDING) != FR_UNDEFINED)
	result->code = f->code;
    if ((f->code & FR_PENDING))
	result->code |= FR_PENDING;
	
    return TRUE;
}

#undef safe_atoi

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

#if DEBUG
static const char *
code_as_string(FilterCode code)
{
    static const char * const names[] = 
    {
	"UNDEFINED",
	"INFORMATION",
	"WARNING",
	"ERROR",
	"BUILDSTART",
	"CHANGEDIR",
	"PUSHDIR",
	"POPDIR",
	"DONE"
    };
    static char buf[128];
    
    buf[0] = '\0';
    if (code & FR_PENDING)
    {
    	code &= ~FR_PENDING;
	strcpy(buf, "PENDING|");
    }
    if (code >= 0 && code <= FR_DONE)
    	strcat(buf, names[code]);
    else
    	sprintf(buf+strlen(buf), "%d (unknown)", code);
	
    return buf;
}
#endif

void
filter_apply(const char *line, FilterResult *result)
{
    GList *fl;
    gboolean matched;
    
    fl = filters_by_lead[(int)line[0]];

    for ( ; fl != 0 ; fl=fl->next)
    {
	Filter *f = (Filter*)fl->data;

	matched = filter_apply_one(f, line, result);
#if DEBUG > 2
	fprintf(stderr, "filter [%s] on \"%s\" -> %s %d (%s) state=\"%s\"\n",
		f->comment,
		line, 
		(matched ? "MATCH" : ""),
		(int)result->code,
		safe_str(result->summary),
		filter_state);
#endif
	if (matched)
	{
#if DEBUG
    	    switch ((result->code & FR_CODE_MASK))
	    {
	    case FR_ERROR:
    	    case FR_WARNING:
	    	fprintf(stderr, "filter [%s] matched \"%s\" as %s\n",
		    	    f->comment, line, code_as_string(result->code));
		break;
	    default:
	    }
#endif
	    return;
	}
    }
    result->code = FR_UNDEFINED;
    filter_state = "";
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

#if DEBUG
static void
filter_post_one(const char *name, FilterStats *fs)
{
    fprintf(stderr, "[%s] %d / %d = %g%%\n",
    	name,
	fs->nmatches,
    	fs->ntries,
	(100.0 * (double)fs->nmatches / (double)fs->ntries));
    memset(fs, 0, sizeof(*fs));
}
#endif

void
filter_post(void)
{
#if DEBUG
    GList *iter;
        
    fprintf(stderr, "filter_post: stats\n");
    
    for (iter = filters ; iter != 0 ; iter = iter->next)
    {
    	Filter *fl = (Filter *)iter->data;

	filter_post_one(fl->comment, &fl->stats);
    }
    filter_post_one("TOTAL", &total_stats);
#endif
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

void
filter_describe_all(estring *e, int lod, const char *indent)
{
    GList *iter1, *iter2;
    
    estring_append_string(e, "Filters:\n");

    for (iter1 = filter_sets ; iter1 != 0 ; iter1 = iter1->next)
    {
    	FilterSet *fs = (FilterSet *)iter1->data;

	estring_append_printf(e, "%s%s\n", indent, fs->name);

    	if (lod > 0)
	{
    	    for (iter2 = fs->filters ; iter2 != 0 ; iter2 = iter2->next)
	    {
		Filter *f = (Filter *)iter2->data;
		
		estring_append_printf(e, "%s%s%s\n", indent, indent, f->comment);
	    }
	}
    }
}

/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
#else /*!HAVE_REGCOMP*/
#error This version of maketool requires the POSIX regcomp function
#endif
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*END*/


syntax highlighted by Code2HTML, v. 0.9.1