/*
 *	Copyright (c) 2007, Thomas Robinson
 *	
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions
 *	are met:
 *	
 *	Redistributions of source code must retain the above copyright
 *	notice, this list of conditions and the following disclaimer.
 *	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.
 *	Neither the name of the tlrobinson.net nor the names of its contributors
 *	may be used to endorse or promote products derived from this software
 *	without specific prior written permission.
 *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *	"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 COPYRIGHT OWNER OR
 *	CONTRIBUTORS 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>

// Linux or Mac OS X
#ifdef __ELF__
#define GCC_FLAGS "-fPIC -shared"
#define EXTENSION "so"
#else
#define GCC_FLAGS "-dynamiclib"
#define EXTENSION "dylib"
#endif

// headers included in compiled code
#define HEADERS "#include <stdio.h>\n#include<math.h>"

typedef double(func_return_double)(double);

int main(int argc, char **argv)
{
	// get our current directory, which we'll use for tmp files (dlopen seems to need absolute paths)
	char *cwd = getcwd(NULL, 0);
	
    unsigned count = 0;
	double result = 0.0;
	
	while (1)
	{
        char tmp_path[1024], command_buffer[1024];

        char input_buffer[1024], code_buffer[2048], function_name[32];
    
		// for unique function and file names (needed for dlopen/dlsym to work correctly)
		count++;
		
		// read in the next line
		printf(">> ");
		fgets(input_buffer, sizeof(input_buffer), stdin);
	
		// format the function name
		sprintf(function_name, "f%d", count);
		
		// format the code string: if it doesn't contain a semicolon, assume it is just an expression
		if (strchr(input_buffer, ';'))
			sprintf(code_buffer, "%s\ndouble %s(double last) { %s\nreturn 0; }", HEADERS, function_name, input_buffer);
		else
			sprintf(code_buffer, "%s\ndouble %s(double last) { return (%s); }", HEADERS, function_name, input_buffer);
			
		// format the filename string, delete the file if it exists
		sprintf(tmp_path, "%s/libtmp%d.%s", cwd, count, EXTENSION);
		unlink(tmp_path);
		
		// format the gcc command string
		sprintf(command_buffer, "gcc -Wall %s -x c - -o %s", GCC_FLAGS, tmp_path);
		
		// execute gcc command, write out the code
	    FILE *fp = popen(command_buffer, "w");
	    fwrite(code_buffer, 1, strlen(code_buffer), fp);
	    fprintf(fp, "\n");
	
		// pclose waits for gcc to terminate (fclose/close do NOT thus compilation will sometimes not finish prior to the dlopen)
		pclose(fp);

        void *lib = NULL;
		void *ptr = NULL;
        
		// open the just-compiled dynamic library
		if ((lib = dlopen(tmp_path, RTLD_NOW|RTLD_LOCAL)) == NULL) {
			puts(dlerror());
		}
		// get the function pointer
		else if ((ptr = dlsym(lib, function_name)) == NULL) {
			puts(dlerror());
		}
		
		// execute it
		if (ptr != NULL)
		{
			func_return_double *func = (func_return_double*)ptr;
			result = (*func)(result);
			// print the result
	    	printf("=> %.*lf\n", (result/((int)result)>1.0)?5:0, result);
		}

		// clean up: close the library, delete the temp file
		dlclose(lib);
		unlink(tmp_path);
	}

	return 0;
}
