%{
/*	$NetBSD: testlang_conf.l,v 1.27 2023/12/10 18:04:55 rillig Exp $ 	*/

/*-
 * Copyright 2009 Brett Lymn <blymn@NetBSD.org>
 * Copyright 2021 Roland Illig <rillig@NetBSD.org>
 *
 * All rights reserved.
 *
 * This code has been donated to The NetBSD Foundation by the Author.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <curses.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <err.h>
#include "returns.h"
#include "testlang_parse.h"

#define MAX_INCLUDES 32 /* limit for the number of nested includes */

int yylex(void);

extern size_t line;
extern char *cur_file;		/* from director.c */

static int include_stack[MAX_INCLUDES];
static char *include_files[MAX_INCLUDES];
static int include_ptr = 0;

static char *
dequote(const char *s, size_t *len)
{
	const unsigned char *p;
	char *buf, *q;

	*len = 0;
	p = (const unsigned char *)s;
	while (*p) {
		if (*p == '\\' && p[1]) {
			if (isdigit(p[1]) && isdigit(p[2]) && isdigit(p[3]))
				p += 3;
			else
				++p;
		}
		++(*len);
		++p;
	}

	buf = malloc(*len + 1);
	if (buf == NULL)
		return NULL;

	p = (const unsigned char *)s;
	q = buf;
	while (*p) {
		if (*p == '\\' && p[1]) {
			++p;

			if (isdigit(p[0])) {
				if (isdigit(p[1]) && isdigit(p[2])) {
					*q++ = (p[0] - '0') * 64 +
					    (p[1] - '0') * 8 +
					    (p[2] - '0');
					p += 3;
				} else {
					errx(2,
					    "%s:%zu: Invalid escape sequence "
					    "'\\%c' in string literal; octal "
					    "numbers must be 3 digits wide",
					    cur_file, line, *p);
				}
				continue;
			}

			switch (*p) {
			case 'b':
				/* backspace */
				*q++ = '\b';
				p++;
				break;

			case 'e':
				/* escape */
				*q++ = '\e';
				p++;
				break;

			case 'n':
				/* newline */
				*q++ = '\n';
				p++;
				break;

			case 'r':
				/* carriage return */
				*q++ = '\r';
				p++;
				break;

			case 't':
				/* tab */
				*q++ = '\t';
				p++;
				break;

			case '\\':
				/* backslash */
				*q++ = '\\';
				p++;
				break;

			default:
				if (isalpha(*p))
					errx(2,
					    "%s:%zu: Invalid escape sequence "
					    "'\\%c' in string literal",
					    cur_file, line, *p);
				*q++ = *p++;
			}
		} else
			*q++ = *p++;
	}
	*q++ = '\0';

	return buf;
}
%}

HEX		0[xX][0-9a-zA-Z]+
STRING		[0-9a-z!#-&(-^ \t%._\\]+
numeric		[-0-9]+
PCHAR           (\\.|[!-~])
ASSIGN		assign
CALL2		call2
CALL3		call3
CALL4		call4
CALL		call
CHECK		check
DELAY		delay
INPUT		input
NOINPUT		noinput
OK_RET		OK
ERR_RET		ERR
COMPARE		compare
COMPAREND	comparend
FILENAME	[A-Za-z0-9.][A-Za-z0-9./_-]+
VARNAME		[A-Za-z][A-Za-z0-9_-]+
NULL_RET	NULL
NON_NULL	NON_NULL
CCHAR		cchar
WCHAR		wchar
BYTE		BYTE
OR		\|
LPAREN		\(
RPAREN		\)
LBRACK		\[
RBRACK		\]
MULTIPLIER	\*
COMMA		,

%x incl
%option noinput nounput

%%

include		BEGIN(incl);

<incl>[ \t]*	/* eat the whitespace */
<incl>[^ \t\n]+ { /* got the include file name */
		char *inc_file;

		if (include_ptr > MAX_INCLUDES) {
			errx(2,
			    "%s:%zu: Maximum number of nested includes "
			    "exceeded", cur_file, line);
		}

		const char *dir_begin;
		int dir_len;
		if (yytext[0] == '/') {
			dir_begin = "";
			dir_len = 0;
		} else {
			dir_begin = cur_file;
			const char *dir_end = strrchr(cur_file, '/');
			if (dir_end != NULL) {
				dir_len = (int)(dir_end + 1 - dir_begin);
			} else {
				dir_begin = ".";
				dir_len = 1;
			}
		}

		if (asprintf(&inc_file, "%.*s%s",
		    dir_len, dir_begin, yytext) == -1)
			err(2, "Cannot construct include path");

		yyin = fopen(inc_file, "r");

		if (!yyin)
			err(1, "Error opening %s", inc_file);

		yypush_buffer_state(yy_create_buffer(yyin, YY_BUF_SIZE));

		include_stack[include_ptr] = line;
		include_files[include_ptr++] = cur_file;
		cur_file = inc_file;
		line = 1;
		BEGIN(INITIAL);
	}

<<EOF>>	{
		yypop_buffer_state();

		if (!YY_CURRENT_BUFFER)
			yyterminate();

		if (--include_ptr < 0)
			errx(2, "Include stack underflow");

		free(cur_file);
		cur_file = include_files[include_ptr];
		line = include_stack[include_ptr];
	}

{ASSIGN}	return ASSIGN;
{CALL2}		return CALL2;
{CALL3}		return CALL3;
{CALL4}		return CALL4;
{CALL}		return CALL;
{CHECK}		return CHECK;
{DELAY}		return DELAY;
{INPUT}		return INPUT;
{NOINPUT}	return NOINPUT;
{COMPARE}	return COMPARE;
{COMPAREND}	return COMPAREND;
{NON_NULL}	return NON_NULL;
{NULL_RET}	return NULL_RET;
{OK_RET}	return OK_RET;
{ERR_RET}	return ERR_RET;
{MULTIPLIER}	return MULTIPLIER;
{COMMA}		return COMMA;
{CCHAR}		return CCHAR;
{WCHAR}		return WCHAR;
{OR}		return OR;
{LPAREN}	return LPAREN;
{RPAREN}	return RPAREN;
{LBRACK}	return LBRACK;
{RBRACK}	return RBRACK;

{HEX}		{
			/* Hex value, convert to decimal and return numeric */
			unsigned long val;

			if (sscanf(yytext, "%lx", &val) != 1)
				errx(1, "Bad hex conversion");

			asprintf(&yylval.string, "%ld", val);
			return numeric;
		}

{numeric}	{
			if ((yylval.string = strdup(yytext)) == NULL)
				err(1, "Cannot allocate numeric string");
			return numeric;
		}

{VARNAME}	{
			if ((yylval.string = strdup(yytext)) == NULL)
				err(1, "Cannot allocate string for varname");
			return VARNAME;
		}

{FILENAME}	{
			size_t len;

			if ((yylval.string = dequote(yytext, &len)) == NULL)
				err(1, "Cannot allocate filename string");
			return FILENAME;
		}

	/* path */
\/{PCHAR}+	{
			size_t len;
			if ((yylval.string = dequote(yytext, &len)) == NULL)
				err(1, "Cannot allocate string");
			return PATH;
		}

\'{STRING}\' 	{
			char *p;
			size_t len;

			if ((yylval.retval = malloc(sizeof(ct_data_t))) == NULL)
				err(1, "Cannot allocate return struct");
			p = yytext;
			p++; /* skip the leading ' */
			if ((yylval.retval->data_value = dequote(p, &len))
			     == NULL)
				err(1, "Cannot allocate string");

			yylval.retval->data_type = data_byte;
			/* trim trailing ' */
			yylval.retval->data_len = len - 1;
			return BYTE;
		}

\`{STRING}\` 	{
			char *p, *str;
			size_t len, chlen;
			size_t i;
			chtype *rv;

			if ((yylval.retval = malloc(sizeof(ct_data_t))) == NULL)
				err(1, "Cannot allocate return struct");
			p = yytext;
			p++; /* skip the leading ` */
			if ((str = dequote(p, &len)) == NULL)
				err(1, "Cannot allocate string");
			len--; /* trim trailing ` */
			if ((len % 2) != 0)
				len--;

			chlen = ((len / 2) + 1) * sizeof(chtype);
			if ((yylval.retval->data_value = malloc(chlen))
			    == NULL)
				err(1, "Cannot allocate chtype array");

			rv = yylval.retval->data_value;
			for (i = 0; i < len; i += 2)
				*rv++ = (str[i] << 8) | str[i+1];
			*rv = __NORMAL | '\0'; /* terminates chtype array */
			yylval.retval->data_type = data_byte;
			yylval.retval->data_len = chlen;
			return BYTE;
		}

\"{STRING}\" 	{
			char *p;
			size_t len;

			p = yytext;
			p++; /* skip the leading " */
			if ((yylval.string = dequote(p, &len)) == NULL)
				err(1, "Cannot allocate string");

			/* remove trailing " */
			yylval.string[len - 1] = '\0';
			return STRING;
		}

\${VARNAME}	{
			char *p;

			p = yytext;
			p++; /* skip $ before var name */
			if ((yylval.string = strdup(p)) == NULL)
				err(1, "Cannot allocate string for varname");
			return VARIABLE;
		}

	/* whitespace, comments */
[ \t\r]		|
#.*		;

^[ \t\r]*#.*\n	|
\\\n		|
^\n		line++;

	/* eol on a line with data. need to process, return eol */
#.*\n		|
\n		{
			line++;
			return EOL;
		}

.		{
			if (isprint((unsigned char)yytext[0]))
				errx(1, "%s:%zu: Invalid character '%c'",
				    cur_file, line + 1, yytext[0]);
			else
				errx(1, "%s:%zu: Invalid character '0x%02x'",
				    cur_file, line + 1, yytext[0]);
		}

%%

int
yywrap(void)
{
	return 1;
}
