/* ast.c */

#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include "types.h"
#include "ast.h"
#include "mem.h"

ast_list *make_list(ast *elem, ast_list *tail)
{
	ast_list *list = safe_malloc(sizeof(ast_list));

	list->elem = elem;
	list->next = tail;

	return list;
}

ast_list *reverse_list(ast_list *list)
{
	ast_list *last = NULL;
	ast_list *next;

	while (list) {
		next = list->next;
		list->next = last;
		last = list;
		list = next;
	}

	return last;
}

ast *make_list_node(ast_list *list)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = node_ast;
	elem->info.node.tag = kind_list;
	elem->info.node.head = list;

	return elem;
}

ast *make_string(char *string)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = str_ast;
	elem->info.string = string;

	return elem;
}

ast *make_integer(long integer)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = int_ast;
	elem->info.integer = integer;

	return elem;
}

ast *make_real(float real)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = real_ast;
	elem->info.real = real;

	return elem;
}

ast *make_function(char *identifier, ast_list *args, ast_list *stmts)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list = NULL;

	elem->tag = node_ast;
	elem->info.node.tag = kind_fndef;
	list = make_list(make_list_node(stmts), list);
	list = make_list(make_list_node(args), list);
	list = make_list(make_string(identifier), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_fndefint(char *identifier, long num)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list = NULL;

	elem->tag = node_ast;
	elem->info.node.tag = kind_fndefint;
	list = make_list(make_integer(num), list);
	list = make_list(make_string(identifier), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_constant(char *identifier, long num)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list = NULL;

	elem->tag = node_ast;
	elem->info.node.tag = kind_constant;
	list = make_list(make_integer(num), list);
	list = make_list(make_string(identifier), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_realconstant(char *identifier, float num)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list = NULL;

	elem->tag = node_ast;
	elem->info.node.tag = kind_constant;
	list = make_list(make_real(num), list);
	list = make_list(make_string(identifier), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_fndefext(char *identifier)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list = NULL;

	elem->tag = node_ast;
	elem->info.node.tag = kind_fndefext;
	list = make_list(make_string(identifier), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_variable(char *identifier)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = var_ast;
	elem->info.variable = identifier;

	return elem;
}

ast *make_array(char *identifier, ast *expression)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = array_ast;
	elem->info.node.tag = kind_array;
	list = make_list(expression, NULL);
	list = make_list(make_variable(identifier), list);
	elem->info.node.head = list;

	return elem;
}

/*
ast *make_assignment(char *identifier, ast *expression)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = node_ast;
	elem->info.node.tag = kind_assign;
	list = make_list(expression, NULL);
	list = make_list(make_variable(identifier), list);
	elem->info.node.head = list;

	return elem;
}
*/

ast *make_assignment(ast_list *identifier_list, ast *expression)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = node_ast;
	elem->info.node.tag = kind_assign;
	list = make_list(expression, NULL);
	list = make_list(make_list_node(identifier_list), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_binary_op(ast_kind op, ast *exp1, ast *exp2)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = node_ast;
	elem->info.node.tag = op;
	list = make_list(exp2, NULL);
	list = make_list(exp1, list);
	elem->info.node.head = list;

	return elem;
}

ast *make_unary_op(ast_kind op, ast *exp1)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = node_ast;
	elem->info.node.tag = op;
	list = make_list(exp1, NULL);
	elem->info.node.head = list;

	return elem;
}

ast *make_call(char *identifier, ast_list *args)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = node_ast;
	elem->info.node.tag = kind_call;
	list = make_list(make_list_node(args), NULL);
	list = make_list(make_string(identifier), list);
	elem->info.node.head = list;

	return elem;
}

ast *make_statement(ast_kind type, ast *expression, ast_list *statements,
    ast_list *more_statements)
{
	ast *elem = safe_malloc(sizeof(ast));
	ast_list *list;

	elem->tag = node_ast;
	elem->info.node.tag = type;
	list = NULL;
	if (more_statements)
		list = make_list(make_list_node(more_statements), list);
	list = make_list(make_list_node(statements), list);
	list = make_list(expression, list);
	elem->info.node.head = list;

	return elem;
}

ast *make_case_statement_variable(char *identifier)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = casevar_ast;
	elem->info.variable = identifier;

	return elem;
}

ast *make_case_statement_number(long num)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = casenum_ast;
	elem->info.integer = num;

	return elem;
}

ast *make_return_statement(void)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = node_ast;
	elem->info.node.tag = stmt_return;
	elem->info.node.head = NULL;

	return elem;
}

ast *make_break_statement(void)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = node_ast;
	elem->info.node.tag = stmt_break;
	elem->info.node.head = NULL;

	return elem;
}

ast *make_default_statement(void)
{
	ast *elem = safe_malloc(sizeof(ast));

	elem->tag = node_ast;
	elem->info.node.tag = stmt_default;
	elem->info.node.head = NULL;

	return elem;
}

void indent(int spaces)
{
	spaces = spaces * 2;
	while (spaces--) {
		printf(" ");
	}
}

void ast_dump_main(ast *, int);

void ast_dump_list(ast_list *list, int spaces)
{
	while (list) {
		ast_dump_main(list->elem, spaces);
		list = list->next;
	}
}

void ast_dump_main(ast *node, int spaces)
{
	indent(spaces);
	switch (node->tag) {
	case int_ast:
		printf("integer: %ld\n", node->info.integer);
		break;
	case real_ast:
		printf("real: %g\n", node->info.real);
		break;
	case var_ast:
		printf("variable: %s\n", node->info.variable);
		break;
	case str_ast:
		printf("string: %s\n", node->info.string);
		break;
	case node_ast:
		switch (node->info.node.tag) {
		case kind_fndef:
			printf("function definition:\n");
			break;
		case kind_assign:
			printf("assignment:\n"); 
			break;
		case kind_list:
			printf("generic list:\n");
			break;
		case kind_call:
			printf("function call:\n");
			break;
		case op_plus:
			printf("plus operation:\n");
			break;
		case op_minus:
			printf("minus operation:\n");
			break;
		case op_times:
			printf("times operation:\n");
			break;
		case op_divide:
			printf("divide operation:\n");
			break;
		case op_gt:
			printf("greater than operation:\n");
			break;
		case op_lt:
			printf("less than operation:\n");
			break;
		case op_ge:
			printf("greater than or equal operation:\n");
			break;
		case op_le:
			printf("less than or equal operation:\n");
			break;
		case op_eq:
			printf("equality operation:\n");
			break;
		case op_ne:
			printf("not equal operation:\n");
			break;
		case stmt_if:
			printf("if statement:\n");
			break;
		case stmt_while:
			printf("while statement:\n");
			break;
		default:
			printf("unknown list type:\n");
			break;
		}

		ast_dump_list(node->info.node.head, spaces+1);
		break;
	default:
		printf("unknown node type\n");
		break;
	}
}

void ast_dump(ast *node)
{
	ast_dump_main(node, 0);
}

