/*	$Id: cunloop.c,v 1.10 2001/07/13 19:09:56 sandro Exp $	*/

/*
 * Copyright (c) 1997-2001 Sandro Sigala.  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.
 *
 * 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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>

#include "config.h"
#include "tokens.h"

#define DEFAULT_PREFIX	"l_"

/* From lexer.c */
#ifdef YYTEXT_POINTER
extern char *yytext;
#else
extern char yytext[];
#endif
extern char *token_buffer;
extern FILE *yyin;
extern int yylex(void);
extern void init_lex(void);
extern void done_lex(void);

static int lookahead;
static FILE *output_file;

static int opt_prefix;			/* Indentifier prefix option. */
static char *opt_prefix_arg;		/* Option argument. */

#define next_token()	(lookahead = yylex())
#define outstr(s)	fputs(s, output_file)
#define outch(c)	fputc(c, output_file)

static void outtk(int tk)
{
	switch (tk) {
	case COMMENT:
	case STRING:
	case DIRECTIVE:
		outstr(token_buffer);
		break;
	case KW_BREAK:
	case KW_CONTINUE:
	case KW_DO:
	case KW_ELSE:
	case KW_FOR:
	case KW_IF:
	case KW_SWITCH:
	case KW_WHILE:
	case KEYWORD:
	case IDENTIFIER:
	case CONSTANT:
	case CHARACTER:
	case OPERATOR:
		outstr(yytext);
		break;
	default:
		outch(tk);
	}
}

static int label_counter;
static int label_continue;
static int label_break;

static int parse_until(int untiltk);

static void do_while(void)
{
	int label_c = ++label_counter;
	int label_b = ++label_counter;
	int save_label_c = label_continue;
	int save_label_b = label_break;

	label_continue = label_c;
	label_break = label_b;

	while (lookahead != '(')
		next_token();
	next_token();

	outstr("{\n");
	fprintf(output_file, "%s%d:\n", opt_prefix_arg, label_c);
	outstr("if (!(");
	parse_until(UNTIL_CLOSEPAREN);
	outstr(")\n");
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_b);
	parse_until(UNTIL_ENDOFINSTR);
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_c);
	fprintf(output_file, "%s%d:;\n", opt_prefix_arg, label_b);
	outstr("}\n");

	label_continue = save_label_c;
	label_break = save_label_b;
}

static void do_do(void)
{
	int label_c = ++label_counter;
	int label_b = ++label_counter;
	int save_label_c = label_continue;
	int save_label_b = label_break;

	label_continue = label_c;
	label_break = label_b;

	outstr("{\n");
	fprintf(output_file, "%s%d:\n", opt_prefix_arg, label_c);
	parse_until(UNTIL_ENDOFINSTR);

	while (lookahead != '(')
		next_token();
	next_token();

        outstr("if (");
	parse_until(UNTIL_CLOSEPAREN);
	fprintf(output_file, "\ngoto %s%d;\n", opt_prefix_arg, label_c);
	fprintf(output_file, "%s%d:;\n", opt_prefix_arg, label_b);
	outstr("}\n");

	label_continue = save_label_c;
	label_break = save_label_b;
}

static void do_for(void)
{
	int label_l = ++label_counter;
	int label_c = ++label_counter;
	int label_b = ++label_counter;
	int label_i = ++label_counter;
	int save_label_c = label_continue;
	int save_label_b = label_break;

	label_continue = label_c;
	label_break = label_b;

	outstr("{\n");

	while (lookahead != '(')
		next_token();
	next_token();

	parse_until(UNTIL_ENDOFINSTR);

	fprintf(output_file, "%s%d:\n", opt_prefix_arg, label_l);
	outstr("if (!(");
	if (!parse_until(UNTIL_ENDOFINSTR_NOECHO))
		fprintf(output_file, "1");
	outstr("))\n");
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_b);
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_i);
	fprintf(output_file, "%s%d:", opt_prefix_arg, label_c);
	parse_until(UNTIL_CLOSEPAREN_NOECHO);
	outstr(";\n");
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_l);
	fprintf(output_file, "%s%d:\n", opt_prefix_arg, label_i);
	parse_until(UNTIL_ENDOFINSTR);
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_c);
	fprintf(output_file, "%s%d:;\n", opt_prefix_arg, label_b);
	outstr("}\n");

	label_continue = save_label_c;
	label_break = save_label_b;
}

static void do_switch(void)
{
	int save_label_b = label_break;

	label_break = 0;

	outstr("switch");
	parse_until(UNTIL_ENDOFINSTR);

	label_break = save_label_b;
}

static void do_if(void)
{
	int label_l = ++label_counter;
	int label_e;

	while (lookahead != '(')
		next_token();
	next_token();

	outstr("{\n");
	outstr("if (!(");
	parse_until(UNTIL_CLOSEPAREN);
	outstr(")\n");
	fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_l);

	parse_until(UNTIL_ENDOFINSTR);

	while (isspace(lookahead))
		next_token();
	if (lookahead == KW_ELSE) {
		label_e = ++label_counter;
		fprintf(output_file, "goto %s%d;\n", opt_prefix_arg, label_e);
	}
	fprintf(output_file, "%s%d:;\n", opt_prefix_arg, label_l);
	if (lookahead == KW_ELSE) {
		next_token();
		parse_until(UNTIL_ENDOFINSTR);
		fprintf(output_file, "%s%d:;\n", opt_prefix_arg, label_e);
	}

	outstr("}\n");
}

/*
 * The main parsing function.
 */
static int parse_until(int untiltk)
{
	int nparens = 0, nblocks = 0;
	int isexpr = 0;

	if (untiltk == UNTIL_CLOSEPAREN || untiltk == UNTIL_CLOSEPAREN_NOECHO)
		nparens++;

	while (lookahead != 0)
		switch (lookahead) {
		case '(':
			next_token();
			isexpr = 1;
			if (nblocks == 0)
				nparens++;
			outch('(');
			break;
		case ')':
			next_token();
			isexpr = 1;
			if (nblocks == 0)
				nparens--;
			if (untiltk == UNTIL_CLOSEPAREN_NOECHO && nparens == 0)
				return isexpr;
			outch(')');
			if (untiltk == UNTIL_CLOSEPAREN && nparens == 0)
				return isexpr;
			break;
		case '{':
			next_token();
			isexpr = 1;
			nblocks++;
			outch('{');
			break;
		case '}':
			next_token();
			isexpr = 1;
			nblocks--;
			outch('}');
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case ';':
			next_token();
			if (untiltk == UNTIL_ENDOFINSTR_NOECHO && nblocks == 0)
				return isexpr;
			outch(';');
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case KW_DO:
			next_token();
			do_do();
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case KW_WHILE:
			next_token();
			do_while();
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case KW_FOR:
			next_token();
			do_for();
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case KW_SWITCH:
			next_token();
			do_switch();
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case KW_IF:
			next_token();
			do_if();
			if (untiltk == UNTIL_ENDOFINSTR && nblocks == 0)
				return isexpr;
			break;
		case KW_BREAK:
			next_token();
			if (label_break > 0)
				fprintf(output_file, "goto %s%d", opt_prefix_arg, label_break);
			else
				outstr("break");
			break;
		case KW_CONTINUE:
			next_token();
			if (label_continue > 0)
				fprintf(output_file, "goto %s%d", opt_prefix_arg, label_continue);
			else
				outstr("continue");
			break;
		default:
			if (!isspace(lookahead))
				isexpr = 1;
			outtk(lookahead);
			next_token();
		}

	return isexpr;
}

static void parse(void)
{
	next_token();
	parse_until(0);
}

static void process_file(char *filename)
{
	if (filename != NULL && strcmp(filename, "-") != 0) {
		if ((yyin = fopen(filename, "r")) == NULL)
			err(1, "%s", filename);
	} else
		yyin = stdin;

	init_lex();
	label_continue = label_break = label_counter = 0;
	parse();
	done_lex();

	if (yyin != stdin)
		fclose(yyin);
}

/*
 * Output the program syntax then exit.
 */
static void usage(void)
{
	fprintf(stderr, "usage: cunroll [-V] [-o file] [-p prefix] [file ...]\n");
	exit(1);
}

/*
 * Used by the err() functions.
 */
char *progname;

int main(int argc, char **argv)
{
	int c;

	progname = argv[0];
	output_file = stdout;

	while ((c = getopt(argc, argv, "Vo:p:")) != -1)
		switch (c) {
		case 'o':
			if (output_file != stdout)
				fclose(output_file);
			if ((output_file = fopen(optarg, "w")) == NULL)
				err(1, "%s", optarg);
			break;
		case 'p':
			opt_prefix = 1;
			opt_prefix_arg = optarg;
			break;
		case 'V':
			fprintf(stderr, "%s\n", CUTILS_VERSION);
			exit(0);
		case '?':
		default:
			usage();
			/* NOTREACHED */
		}
	argc -= optind;
	argv += optind;

	if (!opt_prefix)
		opt_prefix_arg = DEFAULT_PREFIX;

	if (argc < 1)
		process_file(NULL);
	else
		while (*argv)
			process_file(*argv++);

	if (output_file != stdout)
		fclose(output_file);

	return 0;
}
