/* editor syntax highlighting.

   Copyright (C) 1996, 1997, 1998, 2001, 2002, 2003, 2004, 2005, 2006,
   2007 Free Software Foundation, Inc.

   Authors: 1998 Paul Sheer

   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., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.
*/

#include <config.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>

#include <stdlib.h>

#include "../src/global.h"

#include "edit.h"
#include "edit-widget.h"
#include "../src/color.h"	/* use_colors */
#include "../src/main.h"	/* mc_home, mc_home_alt */
#include "../src/wtools.h"	/* message() */

/* bytes */
#define SYNTAX_MARKER_DENSITY 512

/*
   Mispelled words are flushed from the syntax highlighting rules
   when they have been around longer than
   TRANSIENT_WORD_TIME_OUT seconds. At a cursor rate of 30
   chars per second and say 3 chars + a space per word, we can
   accumulate 450 words absolute max with a value of 60. This is
   below this limit of 1024 words in a context.
 */
#define TRANSIENT_WORD_TIME_OUT 60

#define UNKNOWN_FORMAT "unknown"

#define MAX_WORDS_PER_CONTEXT	1024
#define MAX_CONTEXTS		128

#define RULE_ON_LEFT_BORDER 1
#define RULE_ON_RIGHT_BORDER 2

#define SYNTAX_TOKEN_STAR	'\001'
#define SYNTAX_TOKEN_PLUS	'\002'
#define SYNTAX_TOKEN_BRACKET	'\003'
#define SYNTAX_TOKEN_BRACE	'\004'

struct key_word {
    char *keyword;
    unsigned char first;
    char *whole_word_chars_left;
    char *whole_word_chars_right;
    int line_start;
    int color;
};

struct context_rule {
    char *left;
    unsigned char first_left;
    char *right;
    unsigned char first_right;
    char line_start_left;
    char line_start_right;
    int between_delimiters;
    char *whole_word_chars_left;
    char *whole_word_chars_right;
    char *keyword_first_chars;
    int spelling;
    /* first word is word[1] */
    struct key_word **keyword;
};

struct _syntax_marker {
    long offset;
    struct syntax_rule rule;
    struct _syntax_marker *next;
};

int option_syntax_highlighting = 1;
int option_auto_syntax = 1;
char *option_syntax_type = NULL;

#define syntax_g_free(x) do {g_free(x); (x)=0;} while (0)

static gint
mc_defines_destroy (gpointer key, gpointer value, gpointer data)
{
    char **values = value;

    (void) data;

    g_free (key);
    while (*values)
	g_free (*values++);
    g_free (value);

    return FALSE;
}

/* Completely destroys the defines tree */
static inline void
destroy_defines (GTree **defines)
{
    g_tree_traverse (*defines, mc_defines_destroy, G_POST_ORDER, NULL);
    g_tree_destroy (*defines);
    *defines = 0;
}

static void
subst_defines (GTree *defines, char **argv, char **argv_end)
{
    char **t, **p;
    int argc;

    while (*argv && argv < argv_end) {
	if ((t = g_tree_lookup (defines, *argv))) {
	    int count = 0;

	    /* Count argv array members */
	    argc = 0;
	    for (p = &argv[1]; *p; p++)
		argc++;

	    /* Count members of definition array */
	    for (p = t; *p; p++)
		count++;
	    p = &argv[count + argc];

	    /* Buffer overflow or infinitive loop in define */
	    if (p >= argv_end)
		break;

	    /* Move rest of argv after definition members */
	    while (argc >= 0)
		*p-- = argv[argc-- + 1];

	    /* Copy definition members to argv */
	    for (p = argv; *t; *p++ = *t++);
	}
	argv++;
    }
}

static long
compare_word_to_right (WEdit *edit, long i, const char *text,
		       const char *whole_left, const char *whole_right,
		       int line_start)
{
    const unsigned char *p, *q;
    int c, d, j;

    if (!*text)
	return -1;
    c = edit_get_byte (edit, i - 1);
    if (line_start)
	if (c != '\n')
	    return -1;
    if (whole_left)
	if (strchr (whole_left, c))
	    return -1;

    for (p = (unsigned char *) text, q = p + strlen ((char *) p); p < q; p++, i++) {
	switch (*p) {
	case SYNTAX_TOKEN_STAR:
	    if (++p > q)
		return -1;
	    for (;;) {
		c = edit_get_byte (edit, i);
		if (!*p)
		    if (whole_right)
			if (!strchr (whole_right, c))
			    break;
		if (c == *p)
		    break;
		if (c == '\n')
		    return -1;
		i++;
	    }
	    break;
	case SYNTAX_TOKEN_PLUS:
	    if (++p > q)
		return -1;
	    j = 0;
	    for (;;) {
		c = edit_get_byte (edit, i);
		if (c == *p) {
		    j = i;
		    if (*p == *text && !p[1])	/* handle eg '+' and @+@ keywords properly */
			break;
		}
		if (j && strchr ((char *) p + 1, c))	/* c exists further down, so it will get matched later */
		    break;
		if (c == '\n' || c == '\t' || c == ' ') {
		    if (!*p) {
			i--;
			break;
		    }
		    if (!j)
			return -1;
		    i = j;
		    break;
		}
		if (whole_right)
		    if (!strchr (whole_right, c)) {
			if (!*p) {
			    i--;
			    break;
			}
			if (!j)
			    return -1;
			i = j;
			break;
		    }
		i++;
	    }
	    break;
	case SYNTAX_TOKEN_BRACKET:
	    if (++p > q)
		return -1;
	    c = -1;
	    for (;; i++) {
		d = c;
		c = edit_get_byte (edit, i);
		for (j = 0; p[j] != SYNTAX_TOKEN_BRACKET && p[j]; j++)
		    if (c == p[j])
			goto found_char2;
		break;
	      found_char2:
		; /* dummy command */
	    }
	    i--;
	    while (*p != SYNTAX_TOKEN_BRACKET && p <= q)
		p++;
	    if (p > q)
		return -1;
	    if (p[1] == d)
		i--;
	    break;
	case SYNTAX_TOKEN_BRACE:
	    if (++p > q)
		return -1;
	    c = edit_get_byte (edit, i);
	    for (; *p != SYNTAX_TOKEN_BRACE && *p; p++)
		if (c == *p)
		    goto found_char3;
	    return -1;
	  found_char3:
	    while (*p != SYNTAX_TOKEN_BRACE && p < q)
		 p++;
	    break;
	default:
	    if (*p != edit_get_byte (edit, i))
		return -1;
	}
    }
    if (whole_right)
	if (strchr (whole_right, edit_get_byte (edit, i)))
	    return -1;
    return i;
}

static inline const char *xx_strchr (const unsigned char *s, int c)
{
    while (*s >= '\005' && *s != (unsigned char) c) {
	s++;
    }
    return (const char *) s;
}

static inline struct syntax_rule apply_rules_going_right (WEdit * edit, long i, struct syntax_rule rule)
{
    struct context_rule *r;
    int contextchanged = 0, c;
    int found_right = 0, found_left = 0, keyword_foundleft = 0, keyword_foundright = 0;
    int is_end;
    long end = 0;
    struct syntax_rule _rule = rule;

    if (!(c = edit_get_byte (edit, i)))
	return rule;
    is_end = (rule.end == (unsigned char) i);

/* check to turn off a keyword */
    if (_rule.keyword) {
	if (edit_get_byte (edit, i - 1) == '\n')
	    _rule.keyword = 0;
	if (is_end) {
	    _rule.keyword = 0;
	    keyword_foundleft = 1;
	}
    }

/* check to turn off a context */
    if (_rule.context && !_rule.keyword) {
	long e;
	r = edit->rules[_rule.context];
	if (r->first_right == c && !(rule.border & RULE_ON_RIGHT_BORDER) && (e = compare_word_to_right (edit, i, r->right, r->whole_word_chars_left, r->whole_word_chars_right, r->line_start_right)) > 0) {
	    _rule.end = e;
	    found_right = 1;
	    _rule.border = RULE_ON_RIGHT_BORDER;
	    if (r->between_delimiters)
		_rule.context = 0;
	} else if (is_end && rule.border & RULE_ON_RIGHT_BORDER) {

/* always turn off a context at 4 */
	    found_left = 1;
	    _rule.border = 0;
	    if (!keyword_foundleft)
		_rule.context = 0;
	} else if (is_end && rule.border & RULE_ON_LEFT_BORDER) {

/* never turn off a context at 2 */
	    found_left = 1;
	    _rule.border = 0;
	}
    }

/* check to turn on a keyword */
    if (!_rule.keyword) {
	const char *p;

	p = (r = edit->rules[_rule.context])->keyword_first_chars;
	if (p)
	while (*(p = xx_strchr ((unsigned char *) p + 1, c))) {
	    struct key_word *k;
	    int count;
	    long e;

	    count = p - r->keyword_first_chars;
	    k = r->keyword[count];
	    e = compare_word_to_right (edit, i, k->keyword, k->whole_word_chars_left, k->whole_word_chars_right, k->line_start);
	    if (e > 0) {
		end = e;
		_rule.end = e;
		_rule.keyword = count;
		keyword_foundright = 1;
		break;
	    }
	}
    }
/* check to turn on a context */
    if (!_rule.context) {
	if (!found_left && is_end) {
	    if (rule.border & RULE_ON_RIGHT_BORDER) {
		_rule.border = 0;
		_rule.context = 0;
		contextchanged = 1;
		_rule.keyword = 0;

	    } else if (rule.border & RULE_ON_LEFT_BORDER) {
		r = edit->rules[_rule._context];
		_rule.border = 0;
		if (r->between_delimiters) {
		    long e;
		    _rule.context = _rule._context;
		    contextchanged = 1;
		    _rule.keyword = 0;
		    if (r->first_right == c && (e = compare_word_to_right (edit, i, r->right, r->whole_word_chars_left, r->whole_word_chars_right, r->line_start_right)) >= end) {
			_rule.end = e;
			found_right = 1;
			_rule.border = RULE_ON_RIGHT_BORDER;
			_rule.context = 0;
		    }
		}
	    }
	}

	if (!found_right) {
	    int count;
	    struct context_rule **rules = edit->rules;

	    for (count = 1; rules[count]; count++) {
		r = rules[count];
		if (r->first_left == c) {
		    long e;

		    e = compare_word_to_right (edit, i, r->left, r->whole_word_chars_left, r->whole_word_chars_right, r->line_start_left);
		    if (e >= end && (!_rule.keyword || keyword_foundright)) {
			_rule.end = e;
			found_right = 1;
			_rule.border = RULE_ON_LEFT_BORDER;
			_rule._context = count;
			if (!r->between_delimiters)
			    if (!_rule.keyword) {
				_rule.context = count;
				contextchanged = 1;
			    }
			break;
		    }
		}
	    }
	}
    }

/* check again to turn on a keyword if the context switched */
    if (contextchanged && !_rule.keyword) {
	const char *p;

	p = (r = edit->rules[_rule.context])->keyword_first_chars;
	while (*(p = xx_strchr ((unsigned char *) p + 1, c))) {
	    struct key_word *k;
	    int count;
	    long e;

	    count = p - r->keyword_first_chars;
	    k = r->keyword[count];
	    e = compare_word_to_right (edit, i, k->keyword, k->whole_word_chars_left, k->whole_word_chars_right, k->line_start);
	    if (e > 0) {
		_rule.end = e;
		_rule.keyword = count;
		break;
	    }
	}
    }
    return _rule;
}

static struct syntax_rule edit_get_rule (WEdit * edit, long byte_index)
{
    long i;

    if (byte_index > edit->last_get_rule) {
	for (i = edit->last_get_rule + 1; i <= byte_index; i++) {
	    edit->rule = apply_rules_going_right (edit, i, edit->rule);
	    if (i > (edit->syntax_marker ? edit->syntax_marker->offset + SYNTAX_MARKER_DENSITY : SYNTAX_MARKER_DENSITY)) {
		struct _syntax_marker *s;

		s = edit->syntax_marker;
		edit->syntax_marker = g_malloc0 (sizeof (struct _syntax_marker));
		edit->syntax_marker->next = s;
		edit->syntax_marker->offset = i;
		edit->syntax_marker->rule = edit->rule;
	    }
	}
    } else if (byte_index < edit->last_get_rule) {
	struct _syntax_marker *s;

	for (;;) {
	    if (!edit->syntax_marker) {
		memset (&edit->rule, 0, sizeof (edit->rule));
		for (i = -1; i <= byte_index; i++)
		    edit->rule = apply_rules_going_right (edit, i, edit->rule);
		break;
	    }
	    if (byte_index >= edit->syntax_marker->offset) {
		edit->rule = edit->syntax_marker->rule;
		for (i = edit->syntax_marker->offset + 1; i <= byte_index; i++)
		    edit->rule = apply_rules_going_right (edit, i, edit->rule);
		break;
	    }
	    s = edit->syntax_marker->next;
	    syntax_g_free (edit->syntax_marker);
	    edit->syntax_marker = s;
	}
    }
    edit->last_get_rule = byte_index;
    return edit->rule;
}

static void translate_rule_to_color (WEdit * edit, struct syntax_rule rule, int *color)
{
    struct key_word *k;

    k = edit->rules[rule.context]->keyword[rule.keyword];
    *color = k->color;
}

void edit_get_syntax_color (WEdit * edit, long byte_index, int *color)
{
    if (edit->rules && byte_index < edit->last_byte &&
                         option_syntax_highlighting && use_colors) {
	translate_rule_to_color (edit, edit_get_rule (edit, byte_index), color);
    } else {
	*color = use_colors ? EDITOR_NORMAL_COLOR_INDEX : 0;
    }
}


/*
   Returns 0 on error/eof or a count of the number of bytes read
   including the newline. Result must be free'd.
   In case of an error, *line will not be modified.
 */
static int read_one_line (char **line, FILE * f)
{
    GString *p = g_string_new ("");
    int c, r = 0;

    for (;;) {
	c = fgetc (f);
	if (c == EOF) {
	    if (ferror (f)) {
		if (errno == EINTR)
		    continue;
		r = 0;
	    }
	    break;
	}
	r++;
	/* handle all of \r\n, \r, \n correctly. */
	if (c == '\r') {
	    if ( (c = fgetc (f)) == '\n')
		r++;
	    else
		ungetc (c, f);
	    break;
	}
	if (c == '\n')
	    break;

	g_string_append_c (p, c);
    }
    if (r != 0) {
	*line = p->str;
	g_string_free (p, FALSE);
    } else {
	g_string_free (p, TRUE);
    }
    return r;
}

static char *convert (char *s)
{
    char *r, *p;

    p = r = s;
    while (*s) {
	switch (*s) {
	case '\\':
	    s++;
	    switch (*s) {
	    case ' ':
		*p = ' ';
		s--;
		break;
	    case 'n':
		*p = '\n';
		break;
	    case 'r':
		*p = '\r';
		break;
	    case 't':
		*p = '\t';
		break;
	    case 's':
		*p = ' ';
		break;
	    case '*':
		*p = '*';
		break;
	    case '\\':
		*p = '\\';
		break;
	    case '[':
	    case ']':
		*p = SYNTAX_TOKEN_BRACKET;
		break;
	    case '{':
	    case '}':
		*p = SYNTAX_TOKEN_BRACE;
		break;
	    case 0:
		*p = *s;
		return r;
	    default:
		*p = *s;
		break;
	    }
	    break;
	case '*':
	    *p = SYNTAX_TOKEN_STAR;
	    break;
	case '+':
	    *p = SYNTAX_TOKEN_PLUS;
	    break;
	default:
	    *p = *s;
	    break;
	}
	s++;
	p++;
    }
    *p = '\0';
    return r;
}

#define whiteness(x) ((x) == '\t' || (x) == '\n' || (x) == ' ')

static int get_args (char *l, char **args, int args_size)
{
    int argc = 0;

    while (argc < args_size) {
	char *p = l;
	while (*p && whiteness (*p))
	    p++;
	if (!*p)
	    break;
	for (l = p + 1; *l && !whiteness (*l); l++);
	if (*l)
	    *l++ = '\0';
	args[argc++] = convert (p);
    }
    args[argc] = (char *) NULL;
    return argc;
}

#define free_args(x)
#define break_a	{result=line;break;}
#define check_a {if(!*a){result=line;break;}}
#define check_not_a {if(*a){result=line;break;}}

static int
this_try_alloc_color_pair (const char *fg, const char *bg)
{
    char f[80], b[80], *p;

    if (bg)
	if (!*bg)
	    bg = 0;
    if (fg)
	if (!*fg)
	    fg = 0;
    if (fg) {
	g_strlcpy (f, fg, sizeof (f));
	p = strchr (f, '/');
	if (p)
	    *p = '\0';
	fg = f;
    }
    if (bg) {
	g_strlcpy (b, bg, sizeof (b));
	p = strchr (b, '/');
	if (p)
	    *p = '\0';
	bg = b;
    }
    return try_alloc_color_pair (fg, bg);
}

static char *error_file_name = 0;

static FILE *open_include_file (const char *filename)
{
    FILE *f;

    syntax_g_free (error_file_name);
    error_file_name = g_strdup (filename);
    if (*filename == PATH_SEP)
	return fopen (filename, "r");

    g_free (error_file_name);
    error_file_name = g_strconcat (home_dir, PATH_SEP_STR EDIT_DIR PATH_SEP_STR,
				   filename, (char *) NULL);
    f = fopen (error_file_name, "r");
    if (f)
	return f;

    g_free (error_file_name);
    error_file_name = g_strconcat (mc_home, PATH_SEP_STR "syntax" PATH_SEP_STR,
				   filename, (char *) NULL);
    if (!(f = fopen (error_file_name, "r"))) {
        g_free (error_file_name);
        error_file_name = g_strconcat (mc_home_alt, PATH_SEP_STR "syntax" PATH_SEP_STR,
				       filename, (char *) NULL);
    } else return f;
    
    return fopen (error_file_name, "r");
}

/* returns line number on error */
static int
edit_read_syntax_rules (WEdit *edit, FILE *f, char **args, int args_size)
{
    FILE *g = 0;
    char *fg, *bg;
    char last_fg[32] = "", last_bg[32] = "";
    char whole_right[512];
    char whole_left[512];
    char *l = 0;
    int save_line = 0, line = 0;
    struct context_rule **r, *c = 0;
    int num_words = -1, num_contexts = -1;
    int result = 0;
    int argc;
    int i, j;
    int alloc_contexts = MAX_CONTEXTS,
	alloc_words_per_context = MAX_WORDS_PER_CONTEXT,
	max_alloc_words_per_context = MAX_WORDS_PER_CONTEXT;

    args[0] = 0;

    strcpy (whole_left, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_01234567890");
    strcpy (whole_right, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_01234567890");

    r = edit->rules = g_malloc0 (alloc_contexts * sizeof (struct context_rule *));
    edit->rules_count = 0;

    if (!edit->defines)
	edit->defines = g_tree_new ((GCompareFunc) strcmp);

    for (;;) {
	char **a;

	line++;
	l = 0;
	if (!read_one_line (&l, f)) {
	    if (g) {
		fclose (f);
		f = g;
		g = 0;
		line = save_line + 1;
		syntax_g_free (error_file_name);
		syntax_g_free (l);
		if (!read_one_line (&l, f))
		    break;
	    } else {
		break;
	    }
	}
	argc = get_args (l, args, args_size);
	a = args + 1;
	if (!args[0]) {
	    /* do nothing */
	} else if (!strcmp (args[0], "include")) {
	    if (g || argc != 2) {
		result = line;
		break;
	    }
	    g = f;
	    f = open_include_file (args[1]);
	    if (!f) {
		syntax_g_free (error_file_name);
		result = line;
		break;
	    }
	    save_line = line;
	    line = 0;
	} else if (!strcmp (args[0], "wholechars")) {
	    check_a;
	    if (!strcmp (*a, "left")) {
		a++;
		g_strlcpy (whole_left, *a, sizeof (whole_left));
	    } else if (!strcmp (*a, "right")) {
		a++;
		g_strlcpy (whole_right, *a, sizeof (whole_right));
	    } else {
		g_strlcpy (whole_left, *a, sizeof (whole_left));
		g_strlcpy (whole_right, *a, sizeof (whole_right));
	    }
	    a++;
	    check_not_a;
	} else if (!strcmp (args[0], "context")) {
	    check_a;
	    if (num_contexts == -1) {
		if (strcmp (*a, "default")) {	/* first context is the default */
		    break_a;
		}
		a++;
		c = r[0] = g_malloc0 (sizeof (struct context_rule));
		c->left = g_strdup (" ");
		c->right = g_strdup (" ");
		num_contexts = 0;
	    } else {
		/* Terminate previous context.  */
		r[num_contexts - 1]->keyword[num_words] = NULL;
		c = r[num_contexts] = g_malloc0 (sizeof (struct context_rule));
		if (!strcmp (*a, "exclusive")) {
		    a++;
		    c->between_delimiters = 1;
		}
		check_a;
		if (!strcmp (*a, "whole")) {
		    a++;
		    c->whole_word_chars_left = g_strdup (whole_left);
		    c->whole_word_chars_right = g_strdup (whole_right);
		} else if (!strcmp (*a, "wholeleft")) {
		    a++;
		    c->whole_word_chars_left = g_strdup (whole_left);
		} else if (!strcmp (*a, "wholeright")) {
		    a++;
		    c->whole_word_chars_right = g_strdup (whole_right);
		}
		check_a;
		if (!strcmp (*a, "linestart")) {
		    a++;
		    c->line_start_left = 1;
		}
		check_a;
		c->left = g_strdup (*a++);
		check_a;
		if (!strcmp (*a, "linestart")) {
		    a++;
		    c->line_start_right = 1;
		}
		check_a;
		c->right = g_strdup (*a++);
		c->first_left = *c->left;
		c->first_right = *c->right;
	    }
	    c->keyword = g_malloc (alloc_words_per_context * sizeof (struct key_word *));
	    num_words = 1;
	    c->keyword[0] = g_malloc0 (sizeof (struct key_word));
	    subst_defines (edit->defines, a, &args[1024]);
	    fg = *a;
	    if (*a)
		a++;
	    bg = *a;
	    if (*a)
		a++;
	    g_strlcpy (last_fg, fg ? fg : "", sizeof (last_fg));
	    g_strlcpy (last_bg, bg ? bg : "", sizeof (last_bg));
	    c->keyword[0]->color = this_try_alloc_color_pair (fg, bg);
	    c->keyword[0]->keyword = g_strdup (" ");
	    check_not_a;

	    alloc_words_per_context = MAX_WORDS_PER_CONTEXT;
	    if (++num_contexts >= alloc_contexts) {
		struct context_rule **tmp;

		alloc_contexts += 128;
		tmp = g_realloc (r, alloc_contexts * sizeof (struct context_rule *));
		r = tmp;
	    }
	} else if (!strcmp (args[0], "spellcheck")) {
	    if (!c) {
		result = line;
		break;
	    }
	    c->spelling = 1;
	} else if (!strcmp (args[0], "keyword")) {
	    struct key_word *k;

	    if (num_words == -1)
		break_a;
	    check_a;
	    k = r[num_contexts - 1]->keyword[num_words] = g_malloc0 (sizeof (struct key_word));
	    if (!strcmp (*a, "whole")) {
		a++;
		k->whole_word_chars_left = g_strdup (whole_left);
		k->whole_word_chars_right = g_strdup (whole_right);
	    } else if (!strcmp (*a, "wholeleft")) {
		a++;
		k->whole_word_chars_left = g_strdup (whole_left);
	    } else if (!strcmp (*a, "wholeright")) {
		a++;
		k->whole_word_chars_right = g_strdup (whole_right);
	    }
	    check_a;
	    if (!strcmp (*a, "linestart")) {
		a++;
		k->line_start = 1;
	    }
	    check_a;
	    if (!strcmp (*a, "whole")) {
		break_a;
	    }
	    k->keyword = g_strdup (*a++);
	    k->first = *k->keyword;
	    subst_defines (edit->defines, a, &args[1024]);
	    fg = *a;
	    if (*a)
		a++;
	    bg = *a;
	    if (*a)
		a++;
	    if (!fg)
		fg = last_fg;
	    if (!bg)
		bg = last_bg;
	    k->color = this_try_alloc_color_pair (fg, bg);
	    check_not_a;

	    if (++num_words >= alloc_words_per_context) {
		struct key_word **tmp;

		alloc_words_per_context += 1024;

		if (alloc_words_per_context > max_alloc_words_per_context)
		    max_alloc_words_per_context = alloc_words_per_context;

		tmp = g_realloc (c->keyword, alloc_words_per_context * sizeof (struct key_word *));
		c->keyword = tmp;
	    }
	} else if (*(args[0]) == '#') {
	    /* do nothing for comment */
	} else if (!strcmp (args[0], "file")) {
	    break;
	} else if (!strcmp (args[0], "define")) {
	    char *key = *a++;
	    char **argv;

	    if (argc < 3)
		break_a;
	    if ((argv = g_tree_lookup (edit->defines, key))) {
		mc_defines_destroy (NULL, argv, NULL);
	    } else {
		key = g_strdup (key);
	    }
	    argv = g_new (char *, argc - 1);
	    g_tree_insert (edit->defines, key, argv);
	    while (*a) {
		*argv++ = g_strdup (*a++);
	    };
	    *argv = NULL;
	} else {		/* anything else is an error */
	    break_a;
	}
	free_args (args);
	syntax_g_free (l);
    }
    free_args (args);
    syntax_g_free (l);

    /* Terminate context array.  */
    if (num_contexts > 0) {
	r[num_contexts - 1]->keyword[num_words] = NULL;
	r[num_contexts] = NULL;
    }

    if (!edit->rules[0])
	syntax_g_free (edit->rules);

    if (result)
	return result;

    if (num_contexts == -1) {
	return line;
    }
    edit->rules_count = num_contexts;

    {
	char *first_chars, *p;

	first_chars = g_malloc (max_alloc_words_per_context + 2);

	for (i = 0; edit->rules[i]; i++) {
	    c = edit->rules[i];
	    p = first_chars;
	    *p++ = (char) 1;
	    for (j = 1; c->keyword[j]; j++)
		*p++ = c->keyword[j]->first;
	    *p = '\0';
	    c->keyword_first_chars = g_strdup (first_chars);
	}

	g_free (first_chars);
    }

    return result;
}

void edit_free_syntax_rules (WEdit * edit)
{
    size_t i, j;

    if (!edit)
	return;
    if (edit->defines)
	destroy_defines (&edit->defines);
    if (!edit->rules)
	return;

    if (edit->rules_count > 0)
	edit_get_rule (edit, -1);
    syntax_g_free (edit->syntax_type);
    edit->syntax_type = 0;

    for (i = 0; i < edit->rules_count; i++) {
	if (edit->rules[i]->keyword) {
	    for (j = 0; edit->rules[i]->keyword[j]; j++) {
		syntax_g_free (edit->rules[i]->keyword[j]->keyword);
		syntax_g_free (edit->rules[i]->keyword[j]->whole_word_chars_left);
		syntax_g_free (edit->rules[i]->keyword[j]->whole_word_chars_right);
		syntax_g_free (edit->rules[i]->keyword[j]);
	    }
	}
	syntax_g_free (edit->rules[i]->left);
	syntax_g_free (edit->rules[i]->right);
	syntax_g_free (edit->rules[i]->whole_word_chars_left);
	syntax_g_free (edit->rules[i]->whole_word_chars_right);
	syntax_g_free (edit->rules[i]->keyword);
	syntax_g_free (edit->rules[i]->keyword_first_chars);
	syntax_g_free (edit->rules[i]);
    }

    while (edit->syntax_marker) {
	struct _syntax_marker *s = edit->syntax_marker->next;
	syntax_g_free (edit->syntax_marker);
	edit->syntax_marker = s;
    }

    syntax_g_free (edit->rules);
}

/* returns -1 on file error, line number on error in file syntax */
static int
edit_read_syntax_file (WEdit * edit, char ***pnames, const char *syntax_file,
		       const char *editor_file, const char *first_line,
		       const char *type)
{
#define NENTRIES 30
    FILE *f, *g = NULL;
    regex_t r;
    regmatch_t pmatch[1];
    char *args[1024], *l = 0;
    int line = 0;
    int result = 0;
    int count = 0;
    char *lib_file;
    int found = 0;
    char **tmpnames = NULL;
    f = fopen (syntax_file, "r");
    if (!f){
	lib_file = concat_dir_and_file (mc_home, "Syntax");
	f = fopen (lib_file, "r");
	g_free (lib_file);
	if (!f)
	    return -1;
    }

    args[0] = 0;
    for (;;) {
	line++;
	syntax_g_free (l);
	if (!read_one_line (&l, f))
	    break;
	(void)get_args (l, args, 1023);	/* Final NULL */
	if (!args[0])
	    continue;

/* Looking for `include ...` lines before first `file ...` ones */
	if (!found && !strcmp (args[0], "include")) {
	    if (g)
		continue;
	    if (!args[1] || !(g = open_include_file (args[1]))) {
		result = line;
		break;
	    }
	    goto found_type;
	}

/* looking for `file ...' lines only */
	if (strcmp (args[0], "file")) {
	    continue;
	}
	found = 1;

/* must have two args or report error */
	if (!args[1] || !args[2]) {
	    result = line;
	    break;
	}
	if (pnames && *pnames) {

/* 1: just collecting a list of names of rule sets */
/* Reallocate the list if required */
	    if (count % NENTRIES == 0) {
		if ((tmpnames = (char**) g_realloc (*pnames, (count + NENTRIES
		    + 1) * sizeof (char*))) != NULL)
		    *pnames = tmpnames;
		else
		    abort ();
	    }
	    (*pnames)[count++] = g_strdup (args[2]);
	    (*pnames)[count] = NULL;
	} else if (type) {

/* 2: rule set was explicitly specified by the caller */
	    if (!strcmp (type, args[2]))
		goto found_type;
	} else if (editor_file && edit) {

/* 3: auto-detect rule set from regular expressions */
	    int q;
	    if (regcomp (&r, args[1], REG_EXTENDED)) {
		result = line;
		break;
	    }

/* does filename match arg 1 ? */
	    q = !regexec (&r, editor_file, 1, pmatch, 0);
	    regfree (&r);
	    if (!q && args[3]) {
		if (regcomp (&r, args[3], REG_EXTENDED)) {
		    result = line;
		    break;
		}

/* does first line match arg 3 ? */
		q = !regexec (&r, first_line, 1, pmatch, 0);
		regfree (&r);
	    }
	    if (q) {
		int line_error;
		char *syntax_type;
	      found_type:
		syntax_type = args[2];
		line_error = edit_read_syntax_rules (edit, g ? g : f, args, 1023);
		if (line_error) {
		    if (!error_file_name)	/* an included file */
			result = line + line_error;
		    else
			result = line_error;
		} else {
		    syntax_g_free (edit->syntax_type);
		    edit->syntax_type = g_strdup (syntax_type);

/* if there are no rules then turn off syntax highlighting for speed */
		    if (!g && !edit->rules[1])
			if (!edit->rules[0]->keyword[1] && !edit->rules[0]->spelling) {
			    edit_free_syntax_rules (edit);
			    break;
			}
		}
		if (g) {
		    fclose (g);
		    g = NULL;
		} else {
		    break;
		}
	    }
	}
    }
    syntax_g_free (l);
    fclose (f);
    return result;
}

static char *get_first_editor_line (WEdit * edit)
{
    int i;
    static char s[256];

    s[0] = '\0';
    if (!edit)
	return s;
    for (i = 0; i < 255; i++) {
	s[i] = edit_get_byte (edit, i);
	if (s[i] == '\n') {
	    s[i] = '\0';
	    break;
	}
    }
    s[255] = '\0';
    return s;
}

/*
 * Load rules into edit struct.  Either edit or *pnames must be NULL.  If
 * edit is NULL, a list of types will be stored into names.  If type is
 * NULL, then the type will be selected according to the filename.
 */
void
edit_load_syntax (WEdit *edit, char ***pnames, const char *type)
{
    int r;
    char *f = NULL;

    if (option_auto_syntax)
	type = NULL;

    edit_free_syntax_rules (edit);

    if (!use_colors)
	return;

    if (!option_syntax_highlighting && (!pnames || !*pnames))
	return;

    if (edit) {
	if (!edit->filename)
	    return;
	if (!*edit->filename && !type)
	    return;
    }
    f = concat_dir_and_file (home_dir, SYNTAX_FILE);
    r = edit_read_syntax_file (edit, pnames, f, edit ? edit->filename : 0,
			       get_first_editor_line (edit), type);
    if (r == -1) {
	edit_free_syntax_rules (edit);
	message (D_ERROR, _(" Load syntax file "),
		 _(" Cannot open file %s \n %s "), f,
		 unix_error_string (errno));
    } else if (r) {
	edit_free_syntax_rules (edit);
	message (D_ERROR, _(" Load syntax file "),
		 _(" Error in file %s on line %d "),
		 error_file_name ? error_file_name : f, r);
	syntax_g_free (error_file_name);
    } else {
	/* succeeded */
    }
    g_free (f);
}
