/*
    c99tree - parse tree output layer, output format: abstract C
    Copyright (C) 2010  Tibor Palinkas

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "tree.h"
#include "tree_print.h"
#include "c99tree.h"

static void c_print_nodes(const node_t *node, const char *sep, int prec);
static void c_print_node(const node_t *node, int prec);

/* empty string == prec level increase */
char *opname[] = {
/* 0*/	"", /* block expr */
/* 1*/	"", /* exprlist */
/* 2*/	"", /* arg */
/*  */	/* ellipsis and assignment on the same level, since they don't appear together */
/* 3*/	"...", "=", "|=", "^=", "&=", ">>=", "<<=", "+=", "-=", "*=", "/=", "%=", "",
/* 4*/	"?", "",
/* 5*/	"||", "",
/* 6*/	"&&", "",
/* 7*/	"|", "",
/* 8*/	"^", "",
/* 9*/	"&", "",
/*10*/	"==", "!=", "",
/*11*/	"<", ">", "<=", ">=", "",
/*12*/	"<<", ">>", "",
/*13*/	"+", "-", "",
/*14*/	"*", "/", "%", "",
/*15*/	"cast", "",
/*16*/	"&", "*", "+", "-", "~", "!", "pre++", "pre--", "sizeof", "", /* unary */
/*17*/	"[]", ".", "->", "post++", "post--", /* (), cast with initlist */
/*  */	0
};

/* if child prec < parent prec then child needs () */
/* parent prec can be forged in some cases, some child never has () */
static int getprec(const node_t *n) {
	char *name;
	char *mode;
	char **p;
	int prec;

	switch (n->type) {
	case T_EXPRBRACKET: return 0;
	case T_EXPRLIST: return 1;
	case T_INITLIST:
	case T_ARG: return 2; /* >exprlist */
	case T_PTR: return 16;
	case T_INDEX:
	case T_ARGLIST: /* only for decl */
	case T_CALL: return 17; /* post */
	case T_TYPE: return 15; /* <sizeof */
	case T_INIT: return 3; /* assignment */
	case T_CONDITION:
		if (n->name && strcmp(n->name, "condition") != 0)
			return 0; /* in for(cond1;;cond3) no () around assignment */
		return 4; /* >assignment */
	case T_OP: break;
	default: return 0;
	}
	name = strdup(n->name);
	mode = strchr(name, ' ');
	if (mode == NULL)
		goto badop;
	*mode = '\0';
	mode++;
	switch (*mode) {
	case 'u': /* unary */
		switch (*name) {
		case '&': case '*': case '+': case '-':
			free(name);
			return 16;
		}
	case 't': /* ternary */
	case 'b': /* binary */
		break;
	default: badop:
		fprintf(stderr, "ERROR: invalid operator (needs unary/binary/trenary)\n");
		exit(1);
	}
	for (p = opname, prec = 0; *p; p++) {
		if (**p == 0) {
			prec++;
			continue;
		}
		if (strcmp(*p, name) == 0)
			break;
	}
	if (*p == 0) {
		fprintf(stderr, "ERROR: unknown operator: %s\n", n->name);
		exit(1);
	}
	free(name);
	return prec;
}

static void declid(const node_t *n, const char *id, int pprec) {
	int prec;

	if (n == NULL) {
		printf("%s", id);
		return;
	}
	prec = getprec(n);
	switch (n->type) {
	case T_TYPE:
		declid(n->children, id, pprec);
		break;
	case T_PTR:
		if (prec < pprec)
			printf("(");
		printf("*");
		declid(n->next, id, prec);
		if (prec < pprec)
			printf(")");
		break;
	case T_INDEX:
		declid(n->next, id, prec);
		printf("[");
		c_print_nodes(n->children, NULL, 0);
		/* vla f(int n, int a[*]), but not abstract_n */
		if (n->name != NULL)
			if (strcmp(n->name, "*") == 0)
				printf("%s", n->name);
		printf("]");
		break;
	case T_QUALIFIER:
		/* >16 == not pointer */
		if (16 < pprec) {
			fprintf(stderr, "ERROR: unexpected qualifier: %s:%s\n", node_type_names[n->type], n->name);
			exit(1);
		}
		c_print_node(n, prec);
		declid(n->next, id, prec);
		break;
	case T_ARGLIST:
		if ((n->name != NULL) && (strcmp(n->name, "K&R/1") == 0)) {
			if (n->next == NULL) {
				fprintf(stderr, "ERROR: missing K&R/2\n");
				exit(1);
			}
			declid(n->next->next, id, prec); /* must be NULL */
			c_print_node(n, 0); /* K&R/1 */
			c_print_node(n->next, 0); /* K&R/2 */
		} else {
			declid(n->next, id, prec);
			c_print_node(n, 0);
		}
		break;
	case T_INIT: /* fix it later */
		declid(n->next, id, 0);
		break;
	case T_BITS:
		declid(n->next, id, 0);
		c_print_node(n, 0);
		break;
	case T_ASM:
		/* TODO: fix later like init */
		printf("asm(");
		c_print_node(n->children, prec);
		printf(")");
		declid(n->next, id, 0);
		break;
	case T_ATTRLIST:
		/* TODO: fix later like init +precedence */
		c_print_node(n, 0);
		declid(n->next, id, 0);
		break;
	default:
		fprintf(stderr, "ERROR: invalid token in declaration: %s:%s\n", node_type_names[n->type], n->name);
		exit(1);
	}
}

static void c_print_id(const node_t *node)
{
	char *name = node->name && strcmp(node->name, "<anonymous>") != 0 ? node->name : "";

	if (node->children) { /* decl */
		declid(node->children, name, 0);
		{
			node_t *p;
			/* hack to find INIT at the end */
			for (p = node->children; p; p = p->next)
				if (p->type == T_INIT)
					c_print_node(p, 0);
		}
	} else
		printf("%s", name);
}

static void c_print_op(const node_t *node, int prec)
{
	char *mode, *name;

	name = strdup(node->name);
	mode = strchr(name, ' ');
	if (mode == NULL) {
		fprintf(stderr, "ERROR: invalid operator (needs unary/binary/trenary)\n");
		exit(1);
	}
	*mode = '\0';
	mode++;
	if (strcmp(mode, "unary") == 0) {
		if (strcmp(name, "post++") == 0) {
			c_print_node(node->children, prec);
			printf("++");
		} else if (strcmp(name, "post--") == 0) {
			c_print_node(node->children, prec);
			printf("--");
		} else if (strcmp(name, "pre++") == 0) {
			printf("++");
			c_print_node(node->children, prec);
		} else if (strcmp(name, "pre--") == 0) {
			printf("--");
			c_print_node(node->children, prec);
		} else {
			printf("%s", name);
			/* sizeof i */
			if (strcmp(name, "sizeof") == 0)
				printf(" ");
			c_print_node(node->children, prec);
		}
	}
	else if (strcmp(mode, "binary") == 0) {
		if (*name == '[') {
			c_print_node(node->children, prec);
			printf("%c", name[0]);
			c_print_node(node->children->next, 0);
			printf("%c", name[1]);
		} else if (strcmp(name, "cast") == 0) {
			c_print_node(node->children, prec+1); /* type needs () */
			c_print_node(node->children->next, prec);
		} else {
			c_print_node(node->children, prec);
			/* extra space to avoid a-(--b) and 3...4 (gcc extension) */
			printf(" %s ", name);
			c_print_node(node->children->next, prec+1);
		}
	}
	else if (strcmp(mode, "trenary") == 0) {
		c_print_node(node->children, prec+1);
		printf(" ? ");
		c_print_node(node->children->next, 0);
		printf(" : ");
		c_print_node(node->children->next->next, prec);
		}
	else {
		fprintf(stderr, "ERROR: invalid operator (has to be unary/binary/trenart instead of '%s')\n", mode);
		exit(1);
	}
}

static void c_print_statement(const node_t *node)
{
	switch(*node->name) {
		case 'e': /* expr */
			c_print_nodes(node->children, NULL, 1); /* block is () */
			printf(";");
			break;
		case 'f': /* for */
			printf("for(");
			c_print_node(node->children, 0);
			if (node->children->children == NULL || node->children->children->type != T_DECL)
				printf(";");
			c_print_node(node->children->next, 0);
			printf(";");
			c_print_node(node->children->next->next, 0);
			printf(")");
			if (node->children->next->next->next == NULL)
				printf(";");
			else
				c_print_node(node->children->next->next->next, 0);
			break;
		case 'w': /* while */
			printf("while(");
			c_print_node(node->children, 0);
			printf(")");
			if (node->children->next == NULL)
				printf(";");
			else
				c_print_node(node->children->next, 0);
			break;
		case 'd': /* do */
			printf("do ");
			if (node->children->next != NULL) {
				c_print_node(node->children, 0);
				printf("while(");
				c_print_node(node->children->next->children, 0);
				printf(");");
			}
			else {
				printf("; while(");
				c_print_node(node->children->children, 0);
				printf(");");

			}
			break;
		case 'i': /* if */
			printf("if (");
			c_print_node(node->children, 0);
			printf(") ");
			if (node->children->next->children == NULL)
				printf(";");
			else
				c_print_node(node->children->next->children, 0);

			if (node->children->next->next != NULL) {
				printf("else ");
				if (node->children->next->next->children == NULL)
					printf(";");
				else
					c_print_node(node->children->next->next->children, 0);
			}
			break;
		case 's': /* switch */
			printf("switch (");
			c_print_node(node->children, 0);
			printf(") ");
			c_print_node(node->children->next, 0);
			break;
		case 'b': /* break; */
		case 'c': /* continue */
			printf("%s;", node->name);
			break;
		case 'g': /* goto */
		case 'r': /* return */
			printf("%s ", node->name);
			c_print_node(node->children, 0);
			printf(";");
			break;
		case ':': /* asm statements */
		case ',':
			printf("%s", node->name);
			break;
		default:
			fprintf(stderr, "Unknown statement type %s\n", node->name);
			abort();
	}
}

/* if child prec <= parent prec then add parenthesis: (child) */
static void c_print_node(const node_t *node, int pprec)
{
	int prec;

	if (node == NULL)
		return;

	prec = getprec(node);

	switch(node->type) {
		case T_STATEMENT:
			c_print_statement(node);
			break;
		case T_OP:
			/* workaround for -(-i) vs --i, 16 == unary op */
			if (prec < pprec || (prec == 16 && pprec == 16 && (*node->name == '-' || *node->name == '+'))) {
				printf("(");
				c_print_op(node, prec);
				printf(")");
			} else
				c_print_op(node, prec);
			break;
		case T_COMMENT:
			printf("/*%s*/", node->name);
			break;
		case T_PRAGMA:
			printf("\n#pragma %s\n", node->name);
			break;
		case T_ICONST:
		case T_FCONST:
			printf("%s", node->name);
			if (node->children != NULL) {
				fprintf(stderr, "ERROR: unexpected child node of %s:%s\n", node_type_names[node->type], node->name);
				exit(1);
			}
			break;

		case T_SCONST:
			printf("%s", node->name);
			if (node->children != NULL) { /* for asm */
				printf(" (");
				c_print_nodes(node->children, ", ", 2);
				printf(")");
			}
			break;

		case T_QUALIFIER:
		case T_CLASS:
		case T_TYPESPEC:
			if (node->children != NULL) {
				fprintf(stderr, "ERROR: unexpected child node of %s:%s\n", node_type_names[node->type], node->name);
				exit(1);
			}
		case T_TYPECUSTOM:
			printf("%s ", node->name);
			break;
		case T_TYPEOF:
			printf("typeof(");
			c_print_node(node->children, prec);
			printf(")");
			break;
		case T_TYPE:
		case T_FUNCDEF:
		case T_CONDITION:
			if (prec < pprec)
				printf("(");
			c_print_nodes(node->children, NULL, prec);
			if (prec < pprec)
				printf(")");
			break;
		case T_EXPRBRACKET:
			printf("(");
			c_print_nodes(node->children, ", ", prec+1);
			printf(")");
			break;
		case T_EXPRLIST:
			if (prec < pprec)
				printf("(");
			c_print_nodes(node->children, ", ", prec+1);
			if (prec < pprec)
				printf(")");
			break;
		case T_SCONSTLIST:
			c_print_nodes(node->children, " ", prec);
			break;
		case T_TYPEDEF: /* same as decl */
			printf("typedef ");
		case T_ARG: /* either decl or expr, this works for both */
		case T_DECL:
			if (node->children == NULL)
				break;
			c_print_node(node->children, prec);
			printf(" ");
			c_print_nodes(node->children->next, ", ", prec);
			if (node->type != T_ARG)
				printf(";");
			break;
		case T_STRUCT:
			printf("struct %s ", node->name == NULL ? "" : node->name);
			/* empty struct{}, union{} should have a dummy decl child */
			if (node->children) {
				printf("{\n");
				c_print_nodes(node->children, NULL, prec);
				printf("}");
			}
			break;
		case T_UNION:
			printf("union %s ", node->name == NULL ? "" : node->name);
			if (node->children) {
				printf("{\n");
				c_print_nodes(node->children, NULL, prec);
				printf("}");
			}
			break;
		case T_ENUM:
			printf("enum %s ", node->name == NULL ? "" : node->name);
			/* TODO: empty enum{}; */
			if (node->children) {
				printf("{\n");
				c_print_nodes(node->children, ",\n", prec);
				printf("\n}");
			}
			break;
		case T_INIT:
			printf(" = ");
			c_print_nodes(node->children, NULL, prec);
			break;
		case T_TYPENAME:
			printf("%s ", node->name);
			break;
		case T_ID:
			c_print_id(node);
			break;
		case T_ARGLIST:
			if (node->name && strcmp(node->name, "K&R/2") == 0) {
				printf("\n");
				c_print_nodes(node->children, ";", 0);
				if (node->children)
					printf(";\n");
			} else {
				printf("(");
				c_print_nodes(node->children, ", ", 0);
				printf(")");
			}
			break;
		case T_BLOCK:
			if (prec < pprec)
				printf("(");
			if (!node->name || strcmp(node->name, "virtual") != 0)
				printf("{\n");
			c_print_nodes(node->children, "\n", prec);
			if (node->children)
				printf("\n");
			if (!node->name || strcmp(node->name, "virtual") != 0)
				printf("}\n");
			if (prec < pprec)
				printf(")");
			break;
		case T_INITLIST:
			printf("{");
			c_print_nodes(node->children, ", ", prec);
			printf("}");
			break;
		case T_ASM:
			printf("asm (\n");
			c_print_nodes(node->children, ",\n", prec);
			printf("\n);");
			break;
		case T_CALL:
			c_print_nodes(node->children, NULL, prec);
			break;
		case T_LABEL:
			switch(*node->name) {
				case 'd': /* default */
					printf("default:");
					break;
				case 'c': /* case */
					printf("case ");
					c_print_node(node->children, prec);
					printf(":");
					break;
				case 'g': /* goto label*/
					c_print_node(node->children, prec);
					printf(":");
					break;
				default:
					fprintf(stderr, "ERROR: invalid label type %s\n", node->name);
					exit(1);
					break;
			}
			if (node->next == NULL || node->next->type == T_DECL)
				printf(" ;");
			break;
		case T_BITS:
			printf(":");
			c_print_node(node->children, prec);
			break;
		case T_ELLIPSIS:
			printf("...");
			break;
		case T_THEN:
		case T_ELSE:
			fprintf(stderr, "Internal error: unexpected token %s:%s\n", node_type_names[node->type], node->name);
			abort();
		case T_ATTRLIST:
			printf(" __attribute__((");
			c_print_nodes(node->children, ", ", prec);
			printf(")) ");
			break;
		case T_ATTR:
			printf("%s", node->name);
			break;
		case T_INVALID:
		case T_DEBUG:
			/* ignore these */
			break;
		default:
			printf("`%s:%s` ", node_type_names[node->type], node->name);
	}
}

static void c_print_nodes(const node_t *node, const char *sep, int prec)
{
	for(; node != NULL; node = node->next) {
		if (node->loc.is_local)
			c_print_node(node, prec);
		if ((sep != NULL) && (node->next != NULL))
			printf("%s", sep);
	}
}

int Nprint_c(void **nprint_ctx, const char *prefix, const node_t *node, print_state_t state)
{
	switch(state) {
		case PS_HEAD:
			c_print_nodes(node->children, "\n", 0);
			printf("\n"); /* .c must end with \n */
			break;
		case PS_OPENING:
		case PS_TAIL:
		case PS_CLOSING:
			break;
	}
	return 1; /* stop central processing after the root element */
}


