/* arithop.c: Arithmetic and relational operators. */

#define _POSIX_SOURCE

#include <string.h>
#include "x.tab.h"
#include "operator.h"
#include "execute.h"
#include "data.h"
#include "ident.h"
#include "cmstring.h"
#include "util.h"

/* All functions in this file are interpreter opcodes, so they require that the
 * interpreter data (the globals in execute.c) be in a state consistent with
 * interpretation.  They may modify the interpreter data by pushing and popping
 * the data stack or by throwing exceptions. */

/* Effects: Pops the top value on the stack and pushes its logical inverse. */
void op_not(void)
{
    Data *d = &stack[stack_pos - 1];
    int val = !data_true(d);

    /* Replace d with the inverse of its truth value. */
    data_discard(d);
    d->type = INTEGER;
    d->u.val = val;
}

/* Effects: If the top value on the stack is an integer, pops it and pushes its
 *	    its arithmetic inverse. */
void op_negate(void)
{
    Data *d = &stack[stack_pos - 1];

    /* Make sure we're taking the negative of an integer. */
    if (d->type != INTEGER) {
	cthrow(type_id, "Argument (%D) is not an integer.", d);
    } else {
	/* Replace d with -d. */
	d->u.val *= -1;
    }
}

/* Effects: If the top two values on the stack are integers, pops them and
 *	    pushes their product. */
void op_multiply(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	cthrow(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	cthrow(type_id, "Right side (%D) is not an integer.", d2);
    } else {
	/* Replace d1 with d1 * d2, and pop d2. */
	d1->u.val *= d2->u.val;
	pop(1);
    }
}

/* Effects: If the top two values on the stack are integers and the second is
 *	    not zero, pops them, divides the first by the second, and pushes
 *	    the quotient. */
void op_divide(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	cthrow(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	cthrow(type_id, "Right side (%D) is not an integer.", d2);
    } else if (d2->u.val == 0) {
	cthrow(div_id, "Attempt to divide %D by zero.", d1);
    } else {
	/* Replace d1 with d1 / d2, and pop d2. */
	d1->u.val /= d2->u.val;
	pop(1);
    }
}

/* Effects: If the top two values on the stack are integers and the second is
 *	    not zero, pops them, divides the first by the second, and pushes
 *	    the remainder. */
void op_modulo(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	cthrow(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	cthrow(type_id, "Right side (%D) is not an integer.", d2);
    } else if (d2->u.val == 0) {
	cthrow(div_id, "Attempt to divide %D by zero.", d1);
    } else {
	/* Replace d1 with d1 % d2, and pop d2. */
	d1->u.val %= d2->u.val;
	pop(1);
    }
}

/* Effects: If the top two values on the stack are integers, pops them and
 *	    pushes their sum.  If the top two values are strings, pops them,
 *	    concatenates the second onto the first, and pushes the result. */
void op_add(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* If we're adding two integers or two strings, replace d1 with d1+d2 and
     * discard d2. */
    if (d1->type == INTEGER && d2->type == INTEGER) {
	/* Replace d1 with d1 + d2, and pop d2. */
	d1->u.val += d2->u.val;
    } else if (d1->type == STRING && d2->type == STRING) {
	anticipate_assignment();
	d1->u.str = string_add(d1->u.str, d2->u.str);
    } else if (d1->type == LIST && d2->type == LIST) {
	anticipate_assignment();
	d1->u.list = list_append(d1->u.list, d2->u.list);
    } else {
	cthrow(type_id, "Cannot add %D and %D.", d1, d2);
	return;
    }
    pop(1);
}

/* Effects: Adds two lists.  (This is used for [@foo, ...];) */
void op_splice_add(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* No need to check if d2 is a list, due to code generation. */
    if (d1->type != LIST) {
	cthrow(type_id, "%D is not a list.", d1);
	return;
    }

    anticipate_assignment();
    d1->u.list = list_append(d1->u.list, d2->u.list);
    pop(1);
}

/* Effects: If the top two values on the stack are integers, pops them and
 *	    pushes their difference. */
void op_subtract(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	cthrow(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	cthrow(type_id, "Right side (%D) is not an integer.", d2);
    } else {
	/* Replace d1 with d1 - d2, and pop d2. */
	d1->u.val -= d2->u.val;
	pop(1);
    }
}

/* Effects: Pops the top two values on the stack and pushes 1 if they are
 *	    equal, 0 if not. */
void op_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val = (data_cmp(d1, d2) == 0);

    pop(2);
    push_int(val);
}

/* Effects: Pops the top two values on the stack and returns 1 if they are
 *	    unequal, 0 if they are equal. */   
void op_not_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val = (data_cmp(d1, d2) != 0);

    pop(2);
    push_int(val);
}

/* Definition: Two values are comparable if they are of the same type and that
 * 	       type is integer or string. */

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is greater than the second, 0 if not. */
void op_greater(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	cthrow(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	cthrow(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) > 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is greater than or equal to the second, 0 if
 *	    not. */
void op_greater_or_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	cthrow(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	cthrow(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) >= 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is less than the second, 0 if not. */
void op_less(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	cthrow(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	cthrow(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) < 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is greater than or equal to the second, 0 if
 *	    not. */
void op_less_or_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	cthrow(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	cthrow(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) <= 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top value on the stack is a string or a list, pops the top
 *	    two values on the stack and pushes the location of the first value
 *	    in the second (where the first element is 1), or 0 if the first
 *	    value does not exist in the second. */
void op_in(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int pos;
    char *s;

    if (d2->type == LIST) {
	pos = list_search(d2->u.list, d1);
	pop(2);
	push_int(pos + 1);
	return;
    }

    if (d1->type != STRING || d2->type != STRING) {
	cthrow(type_id, "Cannot search for %D in %D.", d1, d2);
	return;
    }

    s = strcstr(string_chars(d2->u.str), string_chars(d1->u.str));
    if (s) {
	pos = s - string_chars(d2->u.str);
    } else {
	pos = -1;
    }

    pop(2);
    push_int(pos + 1);
}