/*
 * Add __initstr to strings in __init functions
 *
 * Copyright (C) 2003 Arnaldo Carvalho de Melo
 *
 *  Licensed under the Open Software License version 1.1
 */
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "token.h"
#include "parse.h"
#include "symbol.h"
#include "expression.h"

static int in_init_data_function;
static int in_exit_data_function;
static int wrapping_string;
static int dont_wrap_section_string;
static int scope;

#if 1
#define INIT_STR_BEGIN "({ static char __str[] __attribute__((__section__" \
		 "(\".init.data\")))="
#define EXIT_STR_BEGIN "({ static char __str[] __attribute__((__section__" \
		 "(\".exit.data\")))="
#define INEXIT_STR_END "; __str;})"
#else
#define INIT_STR_BEGIN "__initstr("
#define EXIT_STR_BEGIN "__exitstr("
#define INEXIT_STR_END ")"
#endif

struct symbol *ident_attribute(struct ident *ident)
{
	struct symbol *sym;

	for (sym = ident->symbols; sym; sym = sym->next_id)
		if (sym->ctype.modifiers & MOD_ATTRIBUTE)
			break;
	return sym;
}

static void handle_ident(struct token *token, struct token *next)
{
	struct ctype ct = { 0, };

	if (!ident_attribute(token->ident))
		goto out;

	attribute_specifier(next, &ct);
	if (ct.modifiers & MOD_SECTION) {
		static char sign_init[] = ".init.text";
		static char sign_exit[] = ".exit.text";

		if (ct.section->length == sizeof(sign_init) &&
		    (!memcmp(ct.section->data, sign_init, sizeof(sign_init)) ||
		     !memcmp(ct.section->data, sign_exit, sizeof(sign_exit)))) {
			in_init_data_function = ct.section->data[1] == 'i'; 
			in_exit_data_function = ct.section->data[1] == 'e'; 
			dont_wrap_section_string = 1;
		}
	}
out:
	printf("%s", show_ident(token->ident));
}

void print_init_wrap(const char *s)
{
	if (!dont_wrap_section_string)
		printf("%s", s);
}

void print_exit_wrap(void)
{
	if (dont_wrap_section_string)
		dont_wrap_section_string = 0;
	else
		printf(INEXIT_STR_END);
}

void initstr_create_builtin_stream(void)
{
	add_pre_buffer("#define __i386__ 1\n");
	add_pre_buffer("#define __linux__ 1\n");
	add_pre_buffer("#define __STDC__ 1\n");
	add_pre_buffer("#define linux linux\n");
	add_pre_buffer("#define __GNUC__ 2\n");
	add_pre_buffer("#define __GNUC_MINOR__ 2.95\n");
}

int main(int argc, char **argv)
{
	int fd;
	char *filename = NULL, **args;
	struct token *token;

	init_symbols();
	
	initstr_create_builtin_stream();

	args = argv;
	for (;;) {
		char *arg = *++args;
		if (!arg)
			break;
		if (arg[0] == '-') {
			args = handle_switch(arg + 1, args);
			continue;
		}
		filename = arg;
	}

	fd = open(filename, O_RDONLY);
	if (fd < 0)
		die("No such file: %s", filename);

	// Tokenize the input stream
	token = tokenize(filename, fd, NULL);
	close(fd);
	// Prepend the initial built-in stream
	token = tokenize_buffer(pre_buffer, pre_buffer_size, token);

	token = preprocess(token);

	while (!eof_token(token)) {
		int prec = 1;
		struct token *next = token->next;
		char *separator = "";

		if (next->pos.whitespace)
			separator = " ";
		if (next->pos.newline) {
			separator = "\n\t\t\t\t\t";
			prec = next->pos.pos;
			if (prec > 4)
				prec = 4;
		}
		switch (token_type(token)) {
		case TOKEN_IDENT:
			handle_ident(token, next);
			break;
		case TOKEN_STRING:
			if (!wrapping_string) {
				wrapping_string = 1;
				if (in_init_data_function)
					print_init_wrap(INIT_STR_BEGIN);
				else if (in_exit_data_function)
					print_init_wrap(EXIT_STR_BEGIN);
				else
					wrapping_string = 0;
			}
			printf("%s", show_string(token->string));
			break;
		case TOKEN_SPECIAL:
			switch (token->special) {
			case '{':
				++scope;
				break;
			case '}':
				--scope;
				if (!scope) {
					in_init_data_function = 0;
					in_exit_data_function = 0;
				} else if (scope < 0)
					die("error: parse error before '}' "
					    "token");
				break;
			case ';':
				if (!scope) {
					in_init_data_function = 0;
					in_exit_data_function = 0;
				}
				break;
			}
			printf("%s", show_special(token->special));
			break;
		default:
			printf("%s", show_token(token));
		}
		printf("%.*s", prec, separator);
		token = next;
		if (wrapping_string &&
		    (eof_token(token) || token_type(token) != TOKEN_STRING)) {
			wrapping_string = 0;
			print_exit_wrap();
		}
	}
	putchar('\n');
	return 0;
}