/*
 * Copyright (c) 2004 Sergey Lyubka
 *
 * Simple example of just-in-time (JIT) compilation.
 * Program accepts single argument, which should be arithmetic expression
 * in reverse Polish notation, for example: ./a.out "44 77 + 2 *"
 * Expression then compiled into the x86 function and called.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>

/* Compilation unit */
struct cu {
	unsigned char	code[512];	/* Generated machine code */
	int		pc;		/* Program counter */
	int		stk;		/* Number of objects in stack */
	int		err;		/* Error */
};

enum {
	ERR_OK,		/* No error */
	ERR_STKOVER,	/* Stack overflow */
	ERR_STKUNDER	/* Stack underflow */
};

static void
elog(int fatal, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        (void) vfprintf(stderr, fmt, ap);
        va_end(ap);

        (void) fputc('\n', stderr);

        if (fatal)
                exit(EXIT_FAILURE);
}

/*
 * End function:
 * Put value from stack into EAX.
 * It should be just one value on stack
 */
static void
leave(struct cu *cup)
{
	if (cup->stk > 1)
		cup->err = ERR_STKUNDER;
	else if (cup->stk < 1)
		cup->err = ERR_STKOVER;
	else {
		cup->code[cup->pc++] = 0x58;	/* POP EAX */
		cup->code[cup->pc++] = 0xc3;	/* RET */
	}
	
	elog(0, "%s", __FUNCTION__);
}

/*
 * Enter a function
 */
static void
enter(struct cu *cup)
{
	cup = NULL;
	elog(0, "%s", __FUNCTION__);
}

/*
 * Push operand on stack
 */
static void
push(struct cu *cup, int value)
{
	cup->code[cup->pc++] = 0x68;	/* PUSH */
	(void) memcpy(&cup->code[cup->pc], &value, 4);
	cup->pc += 4;

	/* Increment number of items on stack */
	cup->stk++;

	elog(0, "%s: %d", __FUNCTION__, value);
}

/*
 * Add two numbers on stack
 */
static void
add(struct cu *cup)
{
	if (cup->stk < 2) {
		cup->err = ERR_STKOVER;
	} else {
		cup->code[cup->pc++] = 0x58;	/* POP EAX */
		cup->code[cup->pc++] = 0x59;	/* POP ECX */
		cup->code[cup->pc++] = 0x01;	/* ADD EAX, ECX */
		cup->code[cup->pc++] = 0xc8;
		cup->code[cup->pc++] = 0x50;	/* PUSH EAX*/
		cup->stk--;
	}
	
	elog(0, "%s", __FUNCTION__);
}

/*
 * Substitute numbers
 */
static void
sub(struct cu *cup)
{
	if (cup->stk < 2) {
		cup->err = ERR_STKOVER;
	} else {
		cup->code[cup->pc++] = 0x59;	/* POP ECX */
		cup->code[cup->pc++] = 0x58;	/* POP EAX */
		cup->code[cup->pc++] = 0x29;	/* SUB EAX, ECX */
		cup->code[cup->pc++] = 0xc8;
		cup->code[cup->pc++] = 0x50;	/* PUSH EAX*/
		cup->stk--;
	}
	
	elog(0, "%s", __FUNCTION__);
}

/*
 * Divide numbers
 */
static void
idiv(struct cu *cup)
{
	if (cup->stk < 2) {
		cup->err = ERR_STKOVER;
	} else {
		cup->code[cup->pc++] = 0x59;	/* POP ECX */
		cup->code[cup->pc++] = 0x58;	/* POP EAX */
		cup->code[cup->pc++] = 0xf7;	/* DIV ECX */
		cup->code[cup->pc++] = 0xf1;
		cup->code[cup->pc++] = 0x50;	/* PUSH EAX*/
		cup->stk--;
	}
	
	elog(0, "%s", __FUNCTION__);
}

/*
 * Multiply two numbers on stack
 */
static void
mul(struct cu *cup)
{
	if (cup->stk < 2) {
		cup->err = ERR_STKOVER;
	} else {
		cup->code[cup->pc++] = 0x58;	/* POP EAX */
		cup->code[cup->pc++] = 0x59;	/* POP ECX */
		cup->code[cup->pc++] = 0xf7;	/* MUL ECX */
		cup->code[cup->pc++] = 0xe1;
		cup->code[cup->pc++] = 0x50;	/* PUSH EAX */
		cup->stk--;
	}
	
	elog(0, "%s", __FUNCTION__);
}

/*
 * Compile given text
 */
static void
compile(struct cu *cup, const char *text)
{
	unsigned	state, number, i = 0;

	/* Initialize compilation unit structure */
	(void) memset(cup, 0, sizeof(*cup));

	enter(cup);

	/* Parse text */
	for (state = 0; ; text++, i++) {

		switch (*text) {
		case '+':
			add(cup);
			break;
		case '*':
			mul(cup);
			break;
		case '/':
			idiv(cup);
			break;
		case '-':
			sub(cup);
			break;
		case '\0':
		case ' ':
		case '\t':
			if (state == 1)
				push(cup, number);
			if (*text == '\0')
				leave(cup);	/* End of text, return */
			state = 0;
			break;
		default:
			if (isdigit(*text)) {
				if (state == 0)
					number = 0;
				number *= 10;
				number += *text - '0';
				state = 1;
				
			} else {
				elog(1, "compile: error: [%s]", text);
			}
			break;
		}

		if (cup->err != ERR_OK)
			elog(1, "compile: error: [%s]: %d", text, cup->err);

		if (*text == '\0')
			break;
	}
}

int
main(int argc, char *argv[])
{
	struct cu	cu;
	int		result = 0;
/*
 * we need this to avoid compiler message
 * ISO C forbids conversion of object pointer to function pointer type
 * - denk
 */
	union {
		int	(*func)(void);
		void	*proc;
	} code;


	if (argc < 2) {
		(void) fprintf(stderr,
		    "usage: %s \"<arith_expr_in_polish_notation>\"\n", argv[0]);
		exit(EXIT_FAILURE);
	} else {
		compile(&cu, argv[1]);
		
		/* Call generated code */
/*		result = ((int (*)(void)) cu.code)(); */
		code.proc = &cu.code;
		result = (*code.func)();

		printf("result: %d\n", result);
	}

	return (EXIT_SUCCESS);
}

