#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"
#include "libtcc.h"

typedef TCCState TCCStateObj;

/* Error handling should store the message and return to the normal execution
 * order. In other words, croak is inappropriate here. */
void my_tcc_error_func (void * context, const char * msg ) {
	/* set the message in the error_message key of the compiler context */
	hv_store((HV*)context, "error_message", 13, newSVpv(msg, 0), 0);
}

typedef void (*my_void_func)(void);

MODULE = C::TinyCompiler           PACKAGE = C::TinyCompiler

############ Creation/Delection ############

void
_create_state(context)
	HV * context
	CODE:
		/* create a new state with error handling */
		TCCState * state = tcc_new();
		if (!state) {
			croak("Unable to create C::TinyCompiler state!\n");
		}
		tcc_set_error_func(state, context, my_tcc_error_func);
		tcc_set_output_type(state, TCC_OUTPUT_MEMORY);
		
		/* Add the state to the context */
		hv_store(context, "_state", 6, newSViv(PTR2IV(state)), 0);

void
DESTROY(context)
	HV * context
	CODE:
		/* Retrieve and delete the state from the context */
		SV * state_sv = hv_delete(context, "_state", 6, 0);
		
		/* The hv_delete will only return non-null if the _state actually 
		 * existed. Of course, it's possible for somebody to create a compiler
		 * state and never actually compile, in which case _state will not be
		 * present. So, make sure we have something. */
		if (state_sv != NULL) {
			/* Free the compiler state memory XXX not thread-safe */
			IV state = SvIV(state_sv);
			tcc_delete(INT2PTR(TCCState *, state));
		}

############ Preprocessor ############

# The next two are pretty much direct copies of each other

void
_add_include_paths(state, ...)
	TCCStateObj * state
	PREINIT:
		char * path_name;
		int i, ret;
	CODE:
		for (i = 1; i < items; i++) {
			path_name = SvPVbyte_nolen(ST(i));
			ret = tcc_add_include_path(state, path_name);
			/* As of this time of writing, tcc_add_include always returns zero,
			 * but if that ever changes, this croak is ready to catch it */
			if (ret < 0) {
				croak("Unkown tcc error including path [%s]\n", path_name);
			}
		}

void
_add_sysinclude_paths(state, ...)
	TCCStateObj * state
	PREINIT:
		char * path_name;
		int i, ret;
	CODE:
		for (i = 1; i < items; i++) {
			path_name = SvPVbyte_nolen(ST(i));
			ret = tcc_add_sysinclude_path(state, path_name);
			/* As of this time of writing, tcc_add_sysinclude always returns
			 * zero, but if that ever changes, this croak is ready to catch it */
			if (ret < 0) {
				croak("Unkown tcc error including syspath [%s]\n", path_name);
			}
		}

void
_define(state, symbol_name, value)
	TCCStateObj * state
	const char * symbol_name
	const char * value
	CODE:
		tcc_define_symbol(state, symbol_name, value);

############ Libraries ############

void
_add_libraries(state, ...)
	TCCStateObj * state
	PREINIT:
		char * lib_name;
		int i;
	CODE:
		for (i = 1; i < items; i++) {
			lib_name = SvPVbyte_nolen(ST(i));
			if (-1 == tcc_add_library(state, lib_name)) {
				/* Returns 0 on success, -1 on failure */
				croak("Unable to add library %s", lib_name);
			}
		}

void
_add_library_paths(state, ...)
	TCCStateObj * state
	PREINIT:
		char * path;
		int i;
	CODE:
		for (i = 1; i < items; i++) {
			path = SvPVbyte_nolen(ST(i));
			tcc_add_library_path(state, path);
		}

############ Compiler ############
void
_compile(state, code)
	TCCStateObj * state
	const char * code
	CODE:
		/* Compile and croak if error */
		int ret = tcc_compile_string(state, code);
		if (ret != 0) croak("Something fishy happened\n");

void
_add_symbols(state, ...)
	TCCStateObj * state
	PREINIT:
		char * symbol_name;
		void * symbol_ptr;
	CODE:
		/* Make sure we've got an even number of arguments (aside from self) */
		if (items % 2 == 0) {
			croak("INTERNAL ERROR: _add_symbols should only get key => value pairs\n");
		}
		int i;
		for (i = 1; i < items; i += 2) {
			symbol_name = SvPVbyte_nolen(ST(i));
			symbol_ptr = INT2PTR(void*, SvIV(ST(i+1)));
			tcc_add_symbol(state, symbol_name, symbol_ptr);
		}

void
_relocate(state)
	TCCStateObj * state
	CODE:
		/* Relocate and croak if error */
		int ret = tcc_relocate(state, TCC_RELOCATE_AUTO);
		if (ret < 0) croak("Relocation error\n");

############ Post-Compiler ############
void
_call_void_function(state, func_name)
	TCCStateObj * state
	const char * func_name
	CODE:
		/* Get a pointer to the function */
		my_void_func p_func = (my_void_func)tcc_get_symbol(state, func_name);
		/* Croak if we encountered errors */
		if (p_func == 0) croak("Unable to locate %s", func_name);
		/* Call it with no inputs and no outputs */
		p_func();

void
_get_symbols(state, ...)
	TCCStateObj * state
	PREINIT:
		char * symbol_name;
		void * symbol_pointer;
		int i;
	PPCODE:
		EXTEND(SP, 2*items);
		for (i = 1; i < items; i++) {
			/* Get the tentative name */
			symbol_name = SvPVbyte_nolen(ST(i));
			
			/* Get a pointer to the symbol */
			symbol_pointer = tcc_get_symbol(state, symbol_name);
			
			/* croak if the symbol retrieval was not successful, as this is
			 * likely to be the result of a typo on the programmer's part */
			if (symbol_pointer == 0) croak("Unable to locate %s", symbol_name);
			
			/* Push the resulting key => value onto the return list */
			PUSHs(sv_2mortal(newSVpv(symbol_name, strlen(symbol_name))));
			PUSHs(sv_2mortal(newSViv(PTR2IV(symbol_pointer))));
		}