/*
 * token.c	Read the next token from a string.
 *		Yes it's pretty primitive but effective.
 *
 * Version:	$Id: 33b858bd62ad7b5e6aece4738e468617885126bf $
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2000,2006  The FreeRADIUS server project
 */

RCSID("$Id: 33b858bd62ad7b5e6aece4738e468617885126bf $")

#include <freeradius-devel/libradius.h>

#include <ctype.h>

const FR_NAME_NUMBER fr_tokens[] = {
	{ "=~", T_OP_REG_EQ,	}, /* order is important! */
	{ "!~", T_OP_REG_NE,	},
	{ "{",	T_LCBRACE,	},
	{ "}",	T_RCBRACE,	},
	{ "(",	T_LBRACE,	},
	{ ")",	T_RBRACE,	},
	{ ",",	T_COMMA,	},
	{ "++",	T_OP_INCRM,	},
	{ "+=",	T_OP_ADD,	},
	{ "-=",	T_OP_SUB,	},
	{ ":=",	T_OP_SET,	},
	{ "=*", T_OP_CMP_TRUE,  },
	{ "!*", T_OP_CMP_FALSE, },
	{ "==",	T_OP_CMP_EQ,	},
	{ "^=", T_OP_PREPEND,	},
	{ "=",	T_OP_EQ,	},
	{ "!=",	T_OP_NE,	},
	{ ">=",	T_OP_GE,	},
	{ ">",	T_OP_GT,	},
	{ "<=",	T_OP_LE,	},
	{ "<",	T_OP_LT,	},
	{ "#",	T_HASH,		},
	{ ";",	T_SEMICOLON,	},
	{ NULL, 0,		},
};

const bool fr_assignment_op[] = {
	false,		/* invalid token */
	false,		/* end of line */
	false,		/* { */
	false,		/* } */
	false,		/* ( */
	false,		/* ) 		 5 */
	false,		/* , */
	false,		/* ; */

	true,		/* ++ */
	true,		/* += */
	true,		/* -=  		10 */
	true,		/* := */
	true,		/* = */
	false,		/* != */
	false,		/* >= */
	false,		/* > 		15 */
	false,		/* <= */
	false,		/* < */
	false,		/* =~ */
	false,		/* !~ */
	false,		/* =* 		20 */
	false,		/* !* */
	false,		/* == */
	true,		/* ^= */
	false,		/* # */
	false,		/* bare word 	25 */
	false,		/* "foo" */
	false,		/* 'foo' */
	false,		/* `foo` */
	false
};

const bool fr_equality_op[] = {
	false,		/* invalid token */
	false,		/* end of line */
	false,		/* { */
	false,		/* } */
	false,		/* ( */
	false,		/* ) 		 5 */
	false,		/* , */
	false,		/* ; */

	false,		/* ++ */
	false,		/* += */
	false,		/* -=  		10 */
	false,		/* := */
	false,		/* = */
	true,		/* != */
	true,		/* >= */
	true,		/* > 		15 */
	true,		/* <= */
	true,		/* < */
	true,		/* =~ */
	true,		/* !~ */
	true,		/* =*		20 */
	true,		/* !* */
	true,		/* == */
	false,		/* ^= */
	false,		/* # */
	false,		/* bare word 	25 */
	false,		/* "foo" */
	false,		/* 'foo' */
	false,		/* `foo` */
	false
};

const bool fr_str_tok[] = {
	false,		/* invalid token */
	false,		/* end of line */
	false,		/* { */
	false,		/* } */
	false,		/* ( */
	false,		/* ) 		 5 */
	false,		/* , */
	false,		/* ; */

	false,		/* ++ */
	false,		/* += */
	false,		/* -=  		10 */
	false,		/* := */
	false,		/* = */
	false,		/* != */
	false,		/* >= */
	false,		/* > 		15 */
	false,		/* <= */
	false,		/* < */
	false,		/* =~ */
	false,		/* !~ */
	false,		/* =* 		20 */
	false,		/* !* */
	false,		/* == */
	false,		/* ^= */
	false,		/* # */
	true,		/* bare word 	25 */
	true,		/* "foo" */
	true,		/* 'foo' */
	true,		/* `foo` */
	false
};

/*
 *	This works only as long as special tokens
 *	are max. 2 characters, but it's fast.
 */
#define TOKEN_MATCH(bptr, tptr) \
	( (tptr)[0] == (bptr)[0] && \
	 ((tptr)[1] == (bptr)[1] || (tptr)[1] == 0))

/*
 *	Read a word from a buffer and advance pointer.
 *	This function knows about escapes and quotes.
 *
 *	At end-of-line, buf[0] is set to '\0'.
 *	Returns 0 or special token value.
 */
static FR_TOKEN getthing(char const **ptr, char *buf, int buflen, bool tok,
			 FR_NAME_NUMBER const *tokenlist, bool unescape)
{
	char			*s;
	char const		*p;
	char			quote;
	bool			end = false;
	unsigned int		x;
	FR_NAME_NUMBER const	*t;
	FR_TOKEN rcode;

	buf[0] = '\0';

	/* Skip whitespace */
	p = *ptr;

	while (*p && isspace((uint8_t) *p)) p++;

	if (!*p) {
		*ptr = p;
		return T_EOL;
	}

	/*
	 *	Might be a 1 or 2 character token.
	 */
	if (tok) for (t = tokenlist; t->name; t++) {
		if (TOKEN_MATCH(p, t->name)) {
			strcpy(buf, t->name);
			p += strlen(t->name);

			rcode = t->number;
			goto done;
		}
	}

	/* Read word. */
	quote = '\0';
	switch (*p) {
	default:
		rcode = T_BARE_WORD;
		break;

	case '\'':
		rcode = T_SINGLE_QUOTED_STRING;
		break;

	case '"':
		rcode = T_DOUBLE_QUOTED_STRING;
		break;

	case '`':
		rcode = T_BACK_QUOTED_STRING;
		break;
	}

	if (rcode != T_BARE_WORD) {
		quote = *p;
		end = false;
		p++;
	}
	s = buf;

	while (*p && buflen-- > 1) {
		/*
		 *	We're looking for strings.  Stop on spaces, or
		 *	(if given a token list), on a token, or on a
		 *	comma.
		 */
		if (!quote) {
			if (isspace((uint8_t) *p)) {
				break;
			}

			if (tok) {
				for (t = tokenlist; t->name; t++) {
					if (TOKEN_MATCH(p, t->name)) {
						*s++ = 0;
						goto done;
					}
				}
			}
			if (*p == ',') break;

			/*
			 *	Copy the character over.
			 */
			*s++ = *p++;
			continue;
		} /* else there was a quotation character */

		/*
		 *	Un-escaped quote character.  We're done.
		 */
		if (*p == quote) {
			end = true;
			p++;
			break;
		}

		/*
		 *	Everything but backslash gets copied over.
		 */
		if (*p != '\\') {
			*s++ = *p++;
			continue;
		}

		/*
		 *	There's nothing after the backslash, it's an error.
		 */
		if (!p[1]) {
			fr_strerror_printf("Unterminated string");
			return T_INVALID;
		}

		if (unescape) {
			p++;

			switch (*p) {
				case 'r':
					*s++ = '\r';
					break;
				case 'n':
					*s++ = '\n';
					break;
				case 't':
					*s++ = '\t';
					break;

				default:
					if (*p >= '0' && *p <= '9' &&
					    sscanf(p, "%3o", &x) == 1) {
						*s++ = x;
						p += 2;
					} else
						*s++ = *p;
					break;
			}
			p++;

		} else {
			/*
			 *	Convert backslash-quote to quote, but
			 *	leave everything else alone.
			 */
			if (p[1] == quote) { /* convert '\'' --> ' */
				p++;
			} else {
				if (buflen < 2) {
					fr_strerror_printf("Truncated input");
					return T_INVALID;
				}

				*(s++) = *(p++);
			}
			*(s++) = *(p++);
		}
	}

	*s++ = 0;

	if (quote && !end) {
		fr_strerror_printf("Unterminated string");
		return T_INVALID;
	}

done:
	/* Skip whitespace again. */
	while (*p && isspace((uint8_t) *p)) p++;

	*ptr = p;

	return rcode;
}

/*
 *	Read a "word" - this means we don't honor
 *	tokens as delimiters.
 */
int getword(char const **ptr, char *buf, int buflen, bool unescape)
{
	return getthing(ptr, buf, buflen, false, fr_tokens, unescape) == T_EOL ? 0 : 1;
}


/*
 *	Read the next word, use tokens as delimiters.
 */
FR_TOKEN gettoken(char const **ptr, char *buf, int buflen, bool unescape)
{
	return getthing(ptr, buf, buflen, true, fr_tokens, unescape);
}

/*
 *	Expect an operator.
 */
FR_TOKEN getop(char const **ptr)
{
	char op[3];
	FR_TOKEN rcode;

	rcode = getthing(ptr, op, sizeof(op), true, fr_tokens, false);
	if (!fr_assignment_op[rcode] && !fr_equality_op[rcode]) {
		fr_strerror_printf("Expected operator");
		return T_INVALID;
	}
	return rcode;
}

/*
 *	Expect a string.
 */
FR_TOKEN getstring(char const **ptr, char *buf, int buflen, bool unescape)
{
	char const *p;

	if (!ptr || !*ptr || !buf) return T_INVALID;

	p = *ptr;

	while (*p && (isspace((uint8_t)*p))) p++;

	*ptr = p;

	if ((*p == '"') || (*p == '\'') || (*p == '`')) {
		return gettoken(ptr, buf, buflen, unescape);
	}

	return getthing(ptr, buf, buflen, false, fr_tokens, unescape);
}

/*
 *	Convert a string to an integer
 */
int fr_str2int(FR_NAME_NUMBER const *table, char const *name, int def)
{
	FR_NAME_NUMBER const *this;

	if (!name) {
		return def;
	}

	for (this = table; this->name != NULL; this++) {
		if (strcasecmp(this->name, name) == 0) {
			return this->number;
		}
	}

	return def;
}

/*
 *	Convert a string matching part of name to an integer.
 */
int fr_substr2int(FR_NAME_NUMBER const *table, char const *name, int def, int len)
{
	FR_NAME_NUMBER const *this;
	size_t max;

	if (!name) {
		return def;
	}

	for (this = table; this->name != NULL; this++) {
		size_t tlen;

		tlen = strlen(this->name);

		/*
		 *	Don't match "request" to user input "req".
		 */
		if ((len > 0) && (len < (int) tlen)) continue;

		/*
		 *	Match up to the length of the table entry if len is < 0.
		 */
		max = (len < 0) ? tlen : (unsigned)len;

		if (strncasecmp(this->name, name, max) == 0) {
			return this->number;
		}
	}

	return def;
}

/*
 *	Convert an integer to a string.
 */
char const *fr_int2str(FR_NAME_NUMBER const *table, int number,
			 char const *def)
{
	FR_NAME_NUMBER const *this;

	for (this = table; this->name != NULL; this++) {
		if (this->number == number) {
			return this->name;
		}
	}

	return def;
}

char const *fr_token_name(int token)
{
	return fr_int2str(fr_tokens, token, "???");
}
