%{
/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * 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. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 "wm.h"
#include "rcfile.tab.h"

/* directory that filenames w/o '/' are refering to */
char *filedir = NULL;

/* name of currently proccessing file */
char *filename = NULL;

/* are we in a forplug included file */
plugin_t *inside_forplug = NULL;

/*
 * entry in the stack of included files; saves all the data
 * for parsing at the point the include is started and restores
 * it on the way out.
 */
typedef struct {
	YY_BUFFER_STATE buffer_state;
	char		*filedir;
	char		*filename;
	int		lineno;
	plugin_t	*inside_forplug;
} incent_t;

/* stack of included files */
static incent_t include_stack[10];
static int include_stacknum = 0;

/* translate \" into " in a string from the rcfile */
static char *rclex_makestr(char *yytext, int yyleng) {
	char *str;
	int len, i;

	/* 
	 * make a copy of the string and chop the " chars.
	 * this copy gets free()'d elsewhere. (yyparse, etc)
	 * switch all the \" into ", \\n into '', and \\ into \
	 */
	str = strdup(&yytext[1]);
	if (!str)
		err(1, "couldn't get memory for string in rclex_makestr");
	len = yyleng - 1;
	str[--len] = '\0';
	for (i = 0; i < len; i++)
		if (str[i] == '\\') {
			if (++i >= len)
				break;
			switch (str[i]) {
			case '\n':
				str[i - 1] = '\n';
				memmove(&str[i - 1], &str[i + 1], len - i + 1);
				len -= 2;
				break;
			case '\"':
				str[i - 1] = '\"';
				memmove(&str[i], &str[i + 1], len - i);
				len--;
				break;
			case '\\':
				str[i - 1] = '\\';
				memmove(&str[i], &str[i + 1], len - i);
				len--;
				break;
			}
		}

	return str;
}

/* start input from new FILE * */
static int rclex_includef(FILE *file, char *incfile,
		char *newfn, plugin_t *forplug) {
	char *tmpstr;

	/* make sure they aren't nesting too deep */
	if (include_stacknum >= sizeof(include_stack) / sizeof(incent_t)) {
		warnx("includes nested too deeply, sorry.");
		return -1;
	}

	/* save all the information we need to save */
	include_stack[include_stacknum].buffer_state = YY_CURRENT_BUFFER;
	if ((tmpstr = strdup(filename)) == NULL)
		err(1, "strdup() failed while trying to include file");
	include_stack[include_stacknum].filename = tmpstr;
	if ((tmpstr = strdup(filedir)) == NULL)
		err(1, "strdup() failed while trying to include file");
	include_stack[include_stacknum].filedir = tmpstr;
	include_stack[include_stacknum].lineno = yylineno;
	include_stack[include_stacknum].inside_forplug = inside_forplug;
	include_stacknum++;

	/* set the values for line num and such for the new buff */
	yylineno = 1;
	strcpy(filename, newfn);
	tmpstr = strrchr(incfile, '/');
	if (tmpstr) {
		*(tmpstr + 1) = '\0';
		if (incfile[0] != '/')
			strncat(filedir, incfile, MAXPATHLEN - strlen(filedir));
		else
			snprintf(filedir, MAXPATHLEN, "%s", incfile);
	}
	inside_forplug = forplug;
	yyin = file;
	yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
#ifndef INITIAL	/* hack; flex docs say INITIAL === 0 */
	BEGIN(0);
#else
	BEGIN(INITIAL);
#endif

	return 0;
}

/* begin parsing a new file; include it */
int rclex_include(char *incfile, plugin_t *forplug) {
	char newfn[MAXPATHLEN];
	FILE *file;

	/* do this kinda early so we can try to open it first */
	if (incfile[0] != '/')
		snprintf(newfn, MAXPATHLEN, "%s%s", filedir, incfile);
	else
		snprintf(newfn, MAXPATHLEN, "%s", incfile);

	/* try to open the file */
	file = fopen(newfn, "r");
	if (!file) {
		warnx("couldn't include %s", newfn);
		return -1;
	}

	/* start parsing it */
	if (rclex_includef(file, incfile, newfn, forplug) == -1) {
		fclose(file);
		return -1;
	}

	return 0;
}

%}

%option yylineno
%option noyywrap
%option nounput

PUNCTS			"("|")"|";"|":"|"{"|"}"|"|"|","
STRING			"\""(("\\\"")|("\\\n")|[^"\""])+"\""
INTEGER			"-"?[0-9]+
FLOAT			"-"?[0-9]+\.[0-9]*
IDENT			[[:upper:][:digit:]_-]*

%x INCLUDE EXEC

%%

"#".*\n			/* eat commentary */

	/*
	 * rcfile directives
	 *
	 * .include - just like CPP #include
	 * .exec - run a program and parse it's output
	 */

^[ \t]*\."include"	BEGIN(INCLUDE);
<INCLUDE>{STRING} {
	char *str;

	str = rclex_makestr(yytext, yyleng);
	rclex_include(str, inside_forplug);

	free(str);
}
<INCLUDE>\n		BEGIN(INITIAL);
<INCLUDE>.		/* eat */

^[ \t]*\."exec"		BEGIN(EXEC);
<EXEC>{STRING} {
	FILE *file;
	char *str;
	int fd[2];

	str = rclex_makestr(yytext, yyleng);

	/*
	 * we make a pipe, and set it as the new yyin val, and
	 * so forth.
	 */
	if (pipe(fd) != 0) {
		warn("unable to make pipe for .exec include");
		free(str);
		YY_BREAK;
	}

	/* fork for our exec include */
	if (!fork()) {
		/* dup the write end to stdout */
		if (dup2(fd[1], STDOUT_FILENO) == -1) {
			warn("couldn't dup descriptor for .exec include");
			exit(1);
		}

		/* close the read end and the nondup'd write descriptor */
		close(fd[0]);
		close(fd[1]);

		execl(_PATH_BSHELL, "sh", "-c", str, NULL);
		exit(1);
	}

	/* now set up the include, close the write end in the parent */
	close(fd[1]);
	file = fdopen(fd[0], "r");
	rclex_includef(file, ".", str, inside_forplug);
	free(str);
}
<EXEC>\n		BEGIN(INITIAL);
<EXEC>.			/* eat */

	/*
	 * data types
	 */

"true"|"yes"		yylval.num = 1;			return BOOLVAL;
"false"|"no"		yylval.num = 0;			return BOOLVAL;
"NoMask"		return NOMASK;
"ShiftMask"		yylval.num = ShiftMask;		return MODIFIER;
"LockMask"		yylval.num = LockMask;		return MODIFIER;
"ControlMask"		yylval.num = ControlMask;	return MODIFIER;
"Mod1Mask"		yylval.num = Mod1Mask;		return MODIFIER;
"Mod2Mask"		yylval.num = Mod2Mask;		return MODIFIER;
"Mod3Mask"		yylval.num = Mod3Mask;		return MODIFIER;
"Mod4Mask"		yylval.num = Mod4Mask;		return MODIFIER;
"Mod5Mask"		yylval.num = Mod5Mask;		return MODIFIER;
{INTEGER}		yylval.num = atoi(yytext);	return INTEGER;
{FLOAT}			yylval.flnum = atof(yytext);	return FLOAT;
{STRING} {
	char *str;

	str = rclex_makestr(yytext, yyleng);
	if (!str) {
		warnx("can't get memory for string");
		YY_BREAK;
	}
	yylval.str = str;
	return STRING;
}

	/*
	 * keywords
	 */

"options"		return OPTIONS;
"forplug"		return FORPLUG;
"plugdat"		return PLUGDAT;
"load"			return LOAD;
"param"			return PARAM;
"fileparam"		return FPARAM;
"style"			return STYLE;
"groups"		return GROUPS;
"pixmaps"		return PIXMAPS;
"keys"			return KEYS;
"decoration"		return DECOR;

	/*
	 * options section
	 */

"desktop_count"		return DESKTOPCNT;
"desktop_width"		return DESKTOPWID;
"desktop_height"	return DESKTOPHEI;
"fullscreen_zoom"	return FULLSCRZOOM;
"mouse_modifier"	return MOUSEMOD;
"anim_delay"		return ANIMDELAY;
"opaquemove"		return OPAQUEMOVE;
"focus_new"		return FOCUSNEW;
"linewidth"		return LINEWIDTH;
"linefg"		return LINEFG;
"wantmitshm"		return WANTMITSHM;
"workspace_slide"	return WSPACESLIDE;
"relative_resize"	return RELRESIZE;
"xinerama_correctloc"	return XINCORRECT;
"placement"		return PLACEMENT;
"place_nonzeros"	return PLACENONZEROS;
"place_transients"	return PLACETRANS;
"place_interactive"	return PLACEINTERACT;
"interact_timeout"	return INTERACTTIME;
"placement_none"	yylval.num = PLACEMENT_NONE;	return PLACETYPE;
"placement_random"	yylval.num = PLACEMENT_RANDOM;	return PLACETYPE;
"placement_pointer"	yylval.num = PLACEMENT_POINTER;	return PLACETYPE;
"placement_smart"	yylval.num = PLACEMENT_SMART;	return PLACETYPE;
"focus"			return FOCUS;
"focus_click"		yylval.num = FOCUS_CLICK;	return FOCUSTYPE;
"focus_sloppy"		yylval.num = FOCUS_SLOPPY;	return FOCUSTYPE;
"focus_pointer"		yylval.num = FOCUS_POINTER;	return FOCUSTYPE;

	/*
	 * keys section
	 */

"key_iconify"		yylval.num = KEY_ICONIFY;	return KEYNAME;
"key_zoom"		yylval.num = KEY_ZOOM;		return KEYNAME;
"key_switchdesktop"	yylval.num = KEY_SWITCHDESK;	return KEYNAME;
"key_moveviewport"	yylval.num = KEY_MOVEVIEWPORT;	return KEYNAME;
"key_setviewport"	yylval.num = KEY_SETVIEWPORT;	return KEYNAME;
"key_command"		yylval.num = KEY_COMMAND;	return KEYNAME;
"key_delete"		yylval.num = KEY_DELETE;	return KEYNAME;
"key_cycle_focus"	yylval.num = KEY_CYCLEFOCUS;	return KEYNAME;
"key_raise"		yylval.num = KEY_RAISE;		return KEYNAME;
"key_lower"		yylval.num = KEY_LOWER;		return KEYNAME;
"key_dgroup_switch"	yylval.num = KEY_DGROUPSWITCH;	return KEYNAME;
"key_sticky"		yylval.num = KEY_STICKY;	return KEYNAME;
"key_abort"		yylval.num = KEY_ABORT;		return KEYNAME;
"key_restart"		yylval.num = KEY_RESTART;	return KEYNAME;
"key_exit"		yylval.num = KEY_EXIT;		return KEYNAME;
"cycle_fwspace"		yylval.num = CF_FWS;		return CYCLETYPE;
"cycle_bwspace"		yylval.num = CF_BWS;		return CYCLETYPE;
"cycle_fscr"		yylval.num = CF_FSCR;		return CYCLETYPE;
"cycle_bscr"		yylval.num = CF_BSCR;		return CYCLETYPE;
"cycle_fall"		yylval.num = CF_FALL;		return CYCLETYPE;
"cycle_ball"		yylval.num = CF_BALL;		return CYCLETYPE;
"mv_up"			yylval.num = MV_UP;		return MVDIR;
"mv_down"		yylval.num = MV_DOWN;		return MVDIR;
"mv_left"		yylval.num = MV_LEFT;		return MVDIR;
"mv_right"		yylval.num = MV_RIGHT;		return MVDIR;
"mv_upright"		yylval.num = MV_UPRIGHT;	return MVDIR;
"mv_downright"		yylval.num = MV_DOWNRIGHT;	return MVDIR;
"mv_downleft"		yylval.num = MV_DOWNLEFT;	return MVDIR;
"mv_upleft"		yylval.num = MV_UPLEFT;		return MVDIR;

	/*
	 * style section
	 */

"title_font"		return TITLEFONT;
"title_color"		return TITLECOLOR;

	/*
	 * groups section
	 */

"title"			return TITLE;
"transient"		return TRANSIENT;
"default"		return DEFAULT;
"internal"		return INTERNAL;

	/*
	 * decoration section
	 */

"dec_full"		yylval.num = DA_FULL;		return DECTYPE;
"dec_near"		yylval.num = DA_NEAR;		return DECTYPE;
"dec_far"		yylval.num = DA_FAR;		return DECTYPE;
"dec_top"		yylval.num = DE_TOP;		return DECEDGE;
"dec_left"		yylval.num = DE_LEFT;		return DECEDGE;
"dec_right"		yylval.num = DE_RIGHT;		return DECEDGE;
"dec_bottom"		yylval.num = DE_BOTTOM;		return DECEDGE;
"act_none"		yylval.num = ACT_NONE;		return ACTION;
"act_move"		yylval.num = ACT_MOVE;		return ACTION;
"act_resize"		yylval.num = ACT_RESIZE;	return ACTION;
"act_delete"		yylval.num = ACT_DELETE;	return ACTION;
"act_iconify"		yylval.num = ACT_ICONIFY;	return ACTION;
"act_zoom"		yylval.num = ACT_ZOOM;		return ACTION;

	/*
	 * misc rules
	 */

{PUNCTS}		return yytext[0];
{IDENT}			yylval.str = strdup(yytext); return IDENT;
.|\n			/* eat anything else */

<<EOF>> {
	plugin_t *plugin;

	/* close the yyin currently in use */
	fclose(yyin);

	/*
	 * come back out a level from the include; if this is
	 * all the way out we stop.
	 */
	if (--include_stacknum < 0)
		yyterminate();
	yy_delete_buffer(YY_CURRENT_BUFFER);
	yy_switch_to_buffer(include_stack[include_stacknum].buffer_state);

	/*
	 * we need to call init on plugins for the forplug stuff.  this is
	 * when we have included a file as a result of a forplug directive
	 * earlier on.
	 */
	plugin = inside_forplug;
	inside_forplug = include_stack[include_stacknum].inside_forplug;
	if (!inside_forplug && plugin) {
		if (plugin->init) {
			plugin_this = plugin;
			if (plugin->init(plugin) != PLUGIN_OK)
				plugin_unload(plugin);
			plugin_this = NULL;
		}
		plugin_subparams_free(&plugin->params);
	}

	/* set up the new file location strings and yylineno */
	snprintf(filedir, MAXPATHLEN, "%s", include_stack[include_stacknum].filedir);
	free(include_stack[include_stacknum].filedir);
	snprintf(filename, MAXPATHLEN, "%s", include_stack[include_stacknum].filename);
	free(include_stack[include_stacknum].filename);
	yylineno = include_stack[include_stacknum].lineno;
}
