/* eval.c: evaluates nawmrc code */

/* Copyright (C) 1999 by the Massachusetts Institute of Technology.
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nawm.h"
#include "lang.h"
#include "parser.h"

nawmval run_fun(node *cmd);
int eval_cmd(node *cmd);
nawmval eval_expr(node *expr);
int eval_comp(int op, dtype type, nawmval left, nawmval right);
nawmval assignval(node *lval, nawmval val);

extern int quit, screenwidth, screenheight;
extern Window root;
extern Display *dpy;

nawmval returnval;

typedef struct _runscope {
  nawmval *slots;
  struct _runscope *parent;
} runscope;

runscope *scopestack;

void initrunscopes(void)
{
  scopestack = NULL;
}

int eval_cmds(node *cmd)
{
  int val;

  while (cmd)
    {
      val = eval_cmd(cmd);
      if (val)
	return val;
      cmd = cmd->next;
    }
  return 0;
}

int eval_cmd(node *cmd)
{
  int ans = 0;

  if (!cmd)
    return 0;

  new_generation();

  switch (cmd->type)
    {
    case RETURN:
      returnval = eval_expr(cmd->vals[0]);
      /* fall through */

    case BREAK:
    case CONTINUE:
      ans = cmd->type;
      break;

    case IF:
      if (eval_expr(cmd->vals[0]))
	ans = eval_cmd(cmd->vals[1]);
      else
	ans = eval_cmd(cmd->vals[2]);
      break;

    case FOR:
      if (cmd->vals[0]->type == FOR)
	{
	  node *fordata = cmd->vals[0];

	  eval_expr(fordata->vals[0]);
	  while (eval_expr(fordata->vals[1]))
	    {
	      if (eval_cmd(cmd->vals[1]) == BREAK)
		break;
	      eval_expr(fordata->vals[2]);
	    }
	}
      else
	{
	  arrayiter ai;
	  array *arr = (array *)eval_expr(cmd->vals[0]->vals[1]);
	  nawmval val;

	  val = assignval(cmd->vals[0]->vals[0], array_first(arr, &ai));
	  while (val)
	    {
	      if (eval_cmd(cmd->vals[1]) == BREAK)
		break;
	      val = assignval(cmd->vals[0]->vals[0], array_next(arr, &ai));
	    }
	}
      break;

    case WHILE:
      while (eval_expr(cmd->vals[0]))
	{
	  if (eval_cmd(cmd->vals[1]) == BREAK)
	    break;
	}
      break;

    case DO:
      do
	{
	  if (eval_cmd(cmd->vals[1]) == BREAK)
	    break;
	}
      while (eval_expr(cmd->vals[0]));
      break;

    case DEL:
      array_delete((array *)eval_expr(cmd->vals[0]), eval_expr(cmd->vals[1]));
      break;

    case CMD:
      run_fun(cmd);
      break;

    case BODY:
      ans = eval_cmds(cmd->vals[0]);
      break;

    default:
      eval_expr(cmd);
      break;
    }

  end_generation();
  return ans;
}

nawmval eval_expr(node *expr)
{
  switch (expr->type)
    {
    case NUM:
    case STR:
      return (nawmval)expr->vals[0];

    case VAR:
      {
	variable *var = (variable *)expr->vals[0];

	if (var->slot == -1)
	  return var->data;
	else
	  return scopestack->slots[var->slot];
      }

    case SUBSCRIPT:
      return array_lookup((array *)eval_expr(expr->vals[0]),
			  eval_expr(expr->vals[1]));

    case ELEMENT:
      return array_size((array *)eval_expr(expr->vals[0]));

    case ASSIGN:
      return assignval(expr->vals[0], eval_expr(expr->vals[1]));

    case PLUS:
      return eval_expr(expr->vals[0]) + eval_expr(expr->vals[1]);
    case NOP:
      return eval_expr(expr->vals[0]);
    case MINUS:
      return eval_expr(expr->vals[0]) - eval_expr(expr->vals[1]);
    case NEGATE:
      return -eval_expr(expr->vals[0]);
    case TIMES:
      return eval_expr(expr->vals[0]) * eval_expr(expr->vals[1]);
    case DIVIDE:
      return eval_expr(expr->vals[0]) / eval_expr(expr->vals[1]);
    case REMAINDER:
      return eval_expr(expr->vals[0]) % eval_expr(expr->vals[1]);
    case AND:
      return eval_expr(expr->vals[0]) && eval_expr(expr->vals[1]);
    case OR:
      return eval_expr(expr->vals[0]) || eval_expr(expr->vals[1]);
    case NOT:
      return !eval_expr(expr->vals[0]);

    case LESS:
    case LESSEQUAL:
    case GREATER:
    case GREATEREQUAL:
    case EQUAL:
    case NOTEQUAL:
      return eval_comp(expr->type, (dtype)expr->vals[2],
		       eval_expr(expr->vals[0]), eval_expr(expr->vals[1]));

    case CONCAT:
      {
	char *left = (char *)eval_expr(expr->vals[0]);
	char *right = (char *)eval_expr(expr->vals[1]);
	char *ans = gcmalloc(strlen(left) + strlen(right) + 1, free);
	sprintf(ans, "%s%s", left, right);
	return (nawmval)ans;
      }

    case IN:
      return array_contains((array *)eval_expr(expr->vals[1]),
			    eval_expr(expr->vals[0]));

    case FUN:
      return run_fun(expr);

    default:
      die("unexpected expression type");
    }
}

int eval_comp(int op, dtype type, nawmval left, nawmval right)
{
  if (type == T_WIN && (left || right))
    {
      Window rootret, parent, *children, cw;
      unsigned int numchildren, n, l = 0, r = 0;

      XQueryTree(dpy, root, &rootret, &parent, &children, &numchildren);
      for (n = 0; n < numchildren; n++)
	{
	  cw = client_window(children[n]);
	  if (cw == (Window)left)
	    l = (nawmval)n;
	  if (cw == (Window)right)
	    r = (nawmval)n;
	}
      XFree(children);
      left = l;
      right = r;
      /* fall through */
    }

  if (type == T_INT || type == T_WIN)
    {
      switch (op)
	{
	case LESS:
	  return left < right;
	case LESSEQUAL:
	  return left <= right;
	case GREATER:
	  return left > right;
	case GREATEREQUAL:
	  return left >= right;
	case EQUAL:
	  return left == right;
	case NOTEQUAL:
	  return left != right;
	}
    }
  else
    {
      switch (op)
	{
	case LESS:
	  return strcmp((char *)left, (char *)right) == -1;
	  break;
	case LESSEQUAL:
	  return strcmp((char *)left, (char *)right) != 1;
	  break;
	case GREATER:
	  return strcmp((char *)left, (char *)right) == 1;
	  break; 
	case GREATEREQUAL: 
	  return strcmp((char *)left, (char *)right) != -1;
	  break;
	case EQUAL:
	  return strcmp((char *)left, (char *)right) == 0;
	  break;
	case NOTEQUAL:
	  return strcmp((char *)left, (char *)right) != 0;
	  break;
	}
    }
  /* can't happen */
  return 0;
}

nawmval run_fun(node *n)
{
  function *fun = (function *)(n->vals[0]);
  nawmval ans = 0; /* Initialize to make purify happy */

  if (fun->numvars != -1)
    {
      runscope *scope;
      int i;
      node *expr;

      scope = xmalloc(sizeof(runscope));
      scope->slots = xmalloc(fun->numvars * sizeof(nawmval));
      memset(scope->slots, 0, fun->numvars * sizeof(nawmval));

      /* assign variables */
      for (i = 0, expr = n->vals[2]; i < fun->numargs; i++, expr = expr->next)
	scope->slots[i] = eval_expr(expr);
      for (; i < fun->numvars; i++)
	scope->slots[i] = initial_value(fun->vartype[i]);

      scope->parent = scopestack;
      scopestack = scope;
      returnval = 0;
      eval_cmds((node *)fun->body);
      ans = returnval;
      returnval = 0;

      scopestack = scope->parent;
      free(scope->slots);
      free(scope);
    }
  else
    {
      nawmval *argv;
      node *arg;
      int i, nargs;

      nargs = (nawmval)n->vals[1];
      argv = xmalloc(nargs * sizeof(nawmval));
      for (i = 0, arg = n->vals[2]; i < nargs; i++, arg = arg->next)
	argv[i] = eval_expr(arg);

      fun->body(nargs, argv, &ans);
      free(argv);
    }

  return ans;
}

nawmval assignval(node *lval, nawmval val)
{
  if (lval->type == VAR)
    {
      variable *var = (variable *)lval->vals[0];
      assign_var(var, val);
    }
  else
    {
      array *arr = (array *)eval_expr(lval->vals[0]);
      array_insert(arr, eval_expr(lval->vals[1]), val);
    }

  return val;
}

void assign_var(variable *var, nawmval val)
{
  nawmval *loc;

  if (var->slot == -1)
    loc = &(var->data);
  else
    loc = &(scopestack->slots[var->slot]);

  if (!is_atomic_type(var->type))
    {
      unref((void *)*loc);
      ref((void *)val);
    }
  *loc = val;
}
