
/********************************************
rexp0.c

libmawk changes (C) 2009-2010, Tibor 'Igor2' Palinkas;
based on mawk code coming with the below copyright:

copyright 1991, Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/

/*  lexical scanner  */

#include  "rexp.h"
#include  "libmawk/zmalloc.h"
#include  <string.h>

/* static functions */
static int do_str(mawk_state_t *MAWK, int, char **, MACHINE *);
static int do_class(mawk_state_t *MAWK, char **, MACHINE *);
static int escape(char **);
static mawk_BV *store_bvp(mawk_state_t *MAWK, mawk_BV *);
static int ctohex(int);


#ifndef	 EG
/* make next array visible */
static
#endif
const char RE_char2token['|' + 1] = {
	0, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
	13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 9, 13, 13, 13,
	6, 7, 3, 4, 13, 13, 10, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
	13, 13, 5, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
	13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 11, 12, 13, 8, 13, 13, 13, 13, 13, 13,
	13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
	13, 13, 13, 13, 1
};

#define	 char2token(x) \
( (unsigned char)(x) > '|' ? T_CHAR : RE_char2token[(int)x] )

#define NOT_STARTED    (-1)

int mawk_RE_lex_init(mawk_state_t *MAWK, char *re)
{
	MAWK->RElp = re;
	MAWK->RElen = strlen(re) + 1;
	MAWK->REprev = NOT_STARTED;
	return mawk_RE_run_stack_init(MAWK);
}


int mawk_RE_lex(mawk_state_t *MAWK, MACHINE *mp)
{
	register int c;

/* reswitch: */
	switch (c = char2token(*MAWK->RElp)) {
	case T_PLUS:
	case T_STAR:
		if (MAWK->REprev == T_START)
			return -6;
		/* fall thru */

	case T_OR:
	case T_Q:
	case T_RP:
		MAWK->RElp++;
		return MAWK->REprev = c;

	case T_SLASH:
		break;

	case 0:
		return 0;

	case T_LP:
		switch (MAWK->REprev) {
		case T_CHAR:
		case T_STR:
		case T_ANY:
		case T_CLASS:
		case T_START:
		case T_RP:
		case T_PLUS:
		case T_STAR:
		case T_Q:
		case T_U:
			return MAWK->REprev = T_CAT;

		default:
			MAWK->RElp++;
			return MAWK->REprev = T_LP;
		}
	}

	/*  *lp  is  an operand, but implicit cat op is possible   */
	switch (MAWK->REprev) {
	case NOT_STARTED:
	case T_OR:
	case T_LP:
	case T_CAT:

		switch (c) {
		case T_ANY:
			{
				static int plus_is_star_flag = 0;

				if (*++MAWK->RElp == '*') {
					MAWK->RElp++;
					*mp = mawk_RE_u(MAWK);
					if (is_invm(*mp))
						return -MEMORY_FAILURE;
					return MAWK->REprev = T_U;
				}
				else if (*MAWK->RElp == '+') {
					if (plus_is_star_flag) {
						MAWK->RElp++;
						*mp = mawk_RE_u(MAWK);
						if (is_invm(*mp))
							return -MEMORY_FAILURE;
						plus_is_star_flag = 0;
						return MAWK->REprev = T_U;
					}
					else {
						plus_is_star_flag = 1;
						MAWK->RElp--;
						*mp = mawk_RE_any(MAWK);
						if (is_invm(*mp))
							return -MEMORY_FAILURE;
						return MAWK->REprev = T_ANY;
					}
				}
				else {
					*mp = mawk_RE_any(MAWK);
					if (is_invm(*mp))
						return -MEMORY_FAILURE;
					MAWK->REprev = T_ANY;
				}
			}
			break;

		case T_SLASH:
			MAWK->RElp++;
			c = escape(&MAWK->RElp);
			MAWK->REprev = do_str(MAWK, c, &MAWK->RElp, mp);
			if (MAWK->REprev < 0)
				return MAWK->REprev;
			break;

		case T_CHAR:
			c = *MAWK->RElp++;
			MAWK->REprev = do_str(MAWK, c, &MAWK->RElp, mp);
			if (MAWK->REprev < 0)
				return MAWK->REprev;
			break;

		case T_CLASS:
			MAWK->REprev = do_class(MAWK, &MAWK->RElp, mp);
			if (MAWK->REprev < 0)
				return MAWK->REprev;
			break;

		case T_START:
			*mp = mawk_RE_start(MAWK);
			if (is_invm(*mp))
				return -MEMORY_FAILURE;
			MAWK->RElp++;
			MAWK->REprev = T_START;
			break;

		case T_END:
			MAWK->RElp++;
			*mp = mawk_RE_end(MAWK);
			if (is_invm(*mp))
				return -MEMORY_FAILURE;
			return MAWK->REprev = T_END;

		default:
			mawk_RE_panic("bad switch in mawk_RE_lex");
		}
		break;

	default:
		/* don't advance the pointer */
		return MAWK->REprev = T_CAT;
	}

	/* check for end character */
	if (*MAWK->RElp == '$') {
		mp->start->type += END_ON;
		MAWK->RElp++;
	}

	return MAWK->REprev;
}

/*
  Collect a run of characters into a string machine.
  If the run ends at *,+, or ?, then don't take the last
  character unless the string has length one.
*/

static int do_str(mawk_state_t *MAWK, int c, char **pp, MACHINE *mp)
/*		 int c;											 the first character */
/*		 char **pp;									 where to put the re_char pointer on exit */
/*		 MACHINE *mp;								 where to put the string machine */
{
	register char *p;							/* runs thru the input */
	char *pt;											/* trails p by one */
	char *str;										/* collect it here */
	register char *s;							/* runs thru the output */
	unsigned len;									/* length collected */
	unsigned alloced;

	p = *pp;
	s = str = mawk_RE_malloc(MAWK, MAWK->RElen);
	if (s == NULL)
		return -MEMORY_FAILURE;
	alloced = MAWK->RElen;
	*s++ = c;
	len = 1;

	while (1) {
		char *save;

		switch (char2token(*p)) {
		case T_CHAR:
			pt = p;
			*s++ = *p++;
			break;

		case T_SLASH:
			pt = p;
			save = p + 1;							/* keep p in a register */
			*s++ = escape(&save);
			p = save;
			break;

		default:
			goto out;
		}
		len++;
	}

out:
	/* if len > 1 and we stopped on a ? + or * , need to back up */
	if (len > 1 && (*p == '*' || *p == '+' || *p == '?')) {
		len--;
		p = pt;
		s--;
	}

	*s = 0;
	*pp = p;
	*mp = mawk_RE_str(MAWK, (char *) mawk_RE_realloc(MAWK, str, alloced, len + 1), len);
	if (is_invm(*mp))
		return -MEMORY_FAILURE;
	return T_STR;
}


/*--------------------------------------------
  BUILD A CHARACTER CLASS
 *---------------------------*/

#define	 on( b, x)  ((b)[(x)>>3] |= ( 1 << ((x)&7) ))

static void block_on(mawk_BV b, int x, int y)
	 /* caller makes sure x<=y and x>0 y>0 */
{
	int lo = x >> 3;
	int hi = y >> 3;
	int r_lo = x & 7;
	int r_hi = y & 7;

	if (lo == hi) {
		b[lo] |= (1 << (r_hi + 1)) - (1 << r_lo);
	}
	else {
		int i;
		for (i = lo + 1; i < hi; i++)
			b[i] = 0xff;
		b[lo] |= (0xff << r_lo);
		b[hi] |= ~(0xff << (r_hi + 1));
	}
}

/* build a mawk_BV for a character class.
   *start points at the '['
   on exit:   *start points at the character after ']'
	      mp points at a machine that recognizes the class
*/

static int do_class(mawk_state_t *MAWK, char **start, MACHINE *mp)
{
	register char *p;
	register mawk_BV *bvp;
	int prev;
	char *q, *t;
	int cnt;
	int comp_flag;

	p = t = (*start) + 1;

	/* []...]  puts ] in a class
	   [^]..]  negates a class with ]
	 */
	if (*p == ']')
		p++;
	else if (*p == '^' && *(p + 1) == ']')
		p += 2;

	while (1) {										/* find the back of the class */
		if (!(q = strchr(p, ']'))) {
			/* no closing bracket */
			return E3;
		}
		p = q - 1;
		cnt = 0;
		while (*p == '\\') {
			cnt++;
			p--;
		}
		if ((cnt & 1) == 0) {
			/* even number of \ */
			break;
		}
		p = q + 1;
	}

	/*  q  now  pts at the back of the class   */
	p = t;
	*start = q + 1;

	bvp = (mawk_BV *) mawk_RE_malloc(MAWK, sizeof(mawk_BV));
	if (bvp == NULL)
		return -MEMORY_FAILURE;
	memset(bvp, 0, sizeof(mawk_BV));

	if (*p == '^') {
		comp_flag = 1;
		p++;
	}
	else
		comp_flag = 0;

	prev = -1;										/* indicates  -  cannot be part of a range  */

	while (p < q) {
		switch (*p) {
		case '\\':

			t = p + 1;
			prev = escape(&t);
			on(*bvp, prev);
			p = t;
			break;

		case '-':

			if (prev == -1 || p + 1 == q) {
				prev = '-';
				on(*bvp, '-');
				p++;
			}
			else {
				int c;
				char *mark = ++p;

				if (*p != '\\')
					c = *(unsigned char *) p++;
				else {
					t = p + 1;
					c = escape(&t);
					p = t;
				}

				if (prev <= c) {
					block_on(*bvp, prev, c);
					prev = -1;
				}
				else {									/* back up */

					p = mark;
					prev = '-';
					on(*bvp, '-');
				}
			}
			break;

		default:
			prev = *(unsigned char *) p++;
			on(*bvp, prev);
			break;
		}
	}

	if (comp_flag) {
		for (p = (char *) bvp; p < (char *) bvp + sizeof(mawk_BV); p++) {
			*p = ~*p;
		}
	}

	/* make sure zero is off */
	(*bvp)[0] &= ~1;

	*mp = mawk_RE_class(MAWK, store_bvp(MAWK, bvp));
	if (is_invm(*mp))
		return -MEMORY_FAILURE;
	return T_CLASS;
}


/* storage for bit vectors so they can be reused ,
   stored in an unsorted linear array
   the array grows as needed
*/

#define		BV_GROWTH	6

static mawk_BV *store_bvp(mawk_state_t *MAWK, mawk_BV *bvp)
{
	register mawk_BV **p;
	unsigned t;


	if (MAWK->REbv_next == MAWK->REbv_limit) {
		/* need to grow */
		if (!MAWK->REbv_base) {
			/* first growth */
			t = 0;
			MAWK->REbv_base = (mawk_BV **) mawk_RE_malloc(MAWK, BV_GROWTH * sizeof(mawk_BV *));
			if (MAWK->REbv_base == NULL)
				return NULL;
			MAWK->REbv_alloced = BV_GROWTH * sizeof(mawk_BV *);
		}
		else {
			t = MAWK->REbv_next - MAWK->REbv_base;
			MAWK->REbv_base = (mawk_BV **) mawk_RE_realloc(MAWK, MAWK->REbv_base, MAWK->REbv_alloced, (t + BV_GROWTH) * sizeof(mawk_BV *));
			MAWK->REbv_alloced = (t + BV_GROWTH) * sizeof(mawk_BV *);
			if (MAWK->REbv_base == NULL)
				return NULL;
		}

		MAWK->REbv_next = MAWK->REbv_base + t;
		MAWK->REbv_limit = MAWK->REbv_next + BV_GROWTH;
	}

	/* put bvp in bv_next as a sentinal */
	*MAWK->REbv_next = bvp;
	p = MAWK->REbv_base;
	while (memcmp(*p, bvp, sizeof(mawk_BV)))
		p++;

	if (p == MAWK->REbv_next) {
		/* it is new */
		MAWK->REbv_next++;
	}
	else {
		/* we already have it */
		mawk_RE_free(MAWK, bvp, sizeof(mawk_BV));
	}

	return *p;
}


/* ----------	convert escape sequences  -------------*/

#define isoctal(x)  ((x)>='0'&&(x)<='7')

#define	 NOT_HEX	16
static const char hex_val['f' - 'A' + 1] = {
	10, 11, 12, 13, 14, 15, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0,
	10, 11, 12, 13, 14, 15
};

/* interpret 1 character as hex */
static int ctohex(register int c)
{
	int t;

	if (c >= '0' && c <= '9')
		return c - '0';
	if (c >= 'A' && c <= 'f' && (t = hex_val[c - 'A']))
		return t;
	return NOT_HEX;
}

#define	 RE_ET_END	    7



/*-----------------
  return the char
  and move the pointer forward
  on entry *s -> at the character after the slash
 *-------------------*/

static int escape(char **start_p)
{
	register char *p = *start_p;
	register unsigned x;
	unsigned xx;
	int i;
	struct {
		char in, out;
	} escape_test[RE_ET_END + 1] = {
		{'n', '\n'},
		{'t', '\t'},
		{'f', '\f'},
		{'b', '\b'},
		{'r', '\r'},
		{'a', '\07'},
		{'v', '\013'},
		{0, 0}
	};


	escape_test[RE_ET_END].in = *p;
	i = 0;
	while (escape_test[i].in != *p)
		i++;
	if (i != RE_ET_END) {
		/* in escape_test table */
		*start_p = p + 1;
		return escape_test[i].out;
	}

	if (isoctal(*p)) {
		x = *p++ - '0';
		if (isoctal(*p)) {
			x = (x << 3) + *p++ - '0';
			if (isoctal(*p))
				x = (x << 3) + *p++ - '0';
		}
		*start_p = p;
		return x & 0xff;
	}

	if (*p == 0)
		return '\\';

	if (*p++ == 'x') {
		if ((x = ctohex(*p)) == NOT_HEX) {
			*start_p = p;
			return 'x';
		}

		/* look for another hex digit */
		if ((xx = ctohex(*++p)) != NOT_HEX) {
			x = (x << 4) + xx;
			p++;
		}

		*start_p = p;
		return x;
	}

	/* anything else \c -> c */
	*start_p = p;
	return *(unsigned char *) (p - 1);
}
