// Buffer write expression -*- c++ -*-

#ifdef __GNUC__
# pragma implementation
#endif // __GNUC__
#include "BufferWrite.h"
#include "BufferType.h"
#include "BufferValue.h"
#include "LeafValue.h"
#include "Printer.h"

/** @file BufferWrite.C
 * Write operation to queues and stacks
 */

/* Copyright © 1999-2002 Marko Mäkelä (msmakela@tcs.hut.fi).

   This file is part of MARIA, a reachability analyzer and model checker
   for high-level Petri nets.

   MARIA is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   MARIA is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

BufferWrite::BufferWrite (class Expression& buffer,
			  class Expression& item,
			  class Expression* i) :
  myBuffer (&buffer), myItem (&item), myIndex (i)
{
  assert (myBuffer && myItem);
  assert (myBuffer->getType ()->getKind () == Type::tBuffer);
  assert (myItem->isAssignable (static_cast<const class BufferType*>
				(myBuffer->getType ())->getItemType ()));
  assert (!myIndex || myIndex->getType ()->getKind () == Type::tCard);
  assert (myBuffer->isBasic () && myItem->isBasic ());
  assert (!myIndex || myIndex->isBasic ());

  setType (*myBuffer->getType ());
}

BufferWrite::~BufferWrite ()
{
  myBuffer->destroy ();
  myItem->destroy ();
  myIndex->destroy ();
}

class Value*
BufferWrite::do_eval (const class Valuation& valuation) const
{
  class Value* buffer = myBuffer->eval (valuation);
  if (!buffer)
    return NULL;
  class Value* item = myItem->eval (valuation);
  if (!item) {
    delete buffer;
    return NULL;
  }

  assert (buffer->getType ().getKind () == Type::tBuffer);
  assert (item->getType ().isAssignable (static_cast<const class BufferType&>
					 (buffer->getType ()).getItemType ()));

  class BufferValue* bv = static_cast<class BufferValue*>(buffer);
  card_t idx = 0, capacity = bv->getCapacity ();
  if (myIndex) {
    if (class Value* iv = myIndex->eval (valuation)) {
      assert (iv->getType ().getKind () == Type::tCard);
      card_t i = card_t (static_cast<const class LeafValue&>(*iv));
      delete iv;
      if (i > capacity) {
	valuation.flag (errBuf, *this);
	delete buffer; delete item;
	return NULL;
      }
      idx = i;
    }
    else {
      delete buffer; delete item;
      return NULL;
    }
  }

  if (capacity == bv->getSize ()) {
    valuation.flag (errBuf, *this);
    delete buffer; delete item;
    return NULL;
  }

  card_t i;
  if (static_cast<const class BufferType*>(getType ())->isStack ())
    for (i = capacity; i > idx; i--)
      (*bv)[i] = (*bv)[i - 1];
  else
    for (i = capacity; i > capacity - idx; i--)
      (*bv)[i] = (*bv)[i - 1];
  (*bv)[i] = item;

  return bv;
}

class Expression*
BufferWrite::ground (const class Valuation& valuation,
		     class Transition* transition,
		     bool declare)
{
  class Expression* buffer = myBuffer->ground (valuation, transition, declare);
  if (!buffer) return NULL;
  class Expression* item = myItem->ground (valuation, transition, declare);
  if (!item) { buffer->destroy (); return NULL; }
  class Expression* i =
    myIndex ? myIndex->ground (valuation, transition, declare) : NULL;

  if (myIndex && !i) {
    buffer->destroy ();
    item->destroy ();
    return NULL;
  }

  assert (valuation.isOK ());

  if (buffer == myBuffer && item == myItem && i == myIndex) {
    buffer->destroy ();
    item->destroy ();
    i->destroy ();
    return copy ();
  }
  else
    return static_cast<class Expression*>
      (new class BufferWrite (*buffer, *item, i))->ground (valuation);
}

class Expression*
BufferWrite::substitute (class Substitution& substitution)
{
  class Expression* buffer = myBuffer->substitute (substitution);
  class Expression* item = myItem->substitute (substitution);
  class Expression* i = myIndex ? myIndex->substitute (substitution) : NULL;

  if (buffer == myBuffer && item == myItem && i == myIndex) {
    buffer->destroy ();
    item->destroy ();
    i->destroy ();
    return copy ();
  }

  return (new class BufferWrite (*buffer, *item, i))->cse ();
}

bool
BufferWrite::depends (const class VariableSet& vars,
		      bool complement) const
{
  return
    myBuffer->depends (vars, complement) ||
    myItem->depends (vars, complement) ||
    (myIndex && myIndex->depends (vars, complement));
}

bool
BufferWrite::forExpressions (bool (*operation)
			     (const class Expression&,void*),
			     void* data) const
{
  return
    (*operation) (*this, data) &&
    myBuffer->forExpressions (operation, data) &&
    myItem->forExpressions (operation, data) &&
    (!myIndex || myIndex->forExpressions (operation, data));
}

#ifdef EXPR_COMPILE
# include "CExpression.h"
# include "Constant.h"

void
BufferWrite::compile (class CExpression& cexpr,
		      unsigned indent,
		      const char* lvalue,
		      const class VariableSet* vars) const
{
  char* buf;
  if (cexpr.getVariable (*myBuffer, buf))
    myBuffer->compile (cexpr, indent, buf, vars);
  char* ixvalue = 0;
  if (myIndex && myIndex->getKind () != Expression::eConstant &&
      cexpr.getVariable (*myIndex, ixvalue))
    myIndex->compile (cexpr, indent, ixvalue, vars);
  char* itvalue = 0;
  if (cexpr.getVariable (*myItem, itvalue))
    myItem->compile (cexpr, indent, itvalue, vars);
  class StringBuffer& out = cexpr.getOut ();
  out.indent (indent);
  out.append ("if (");
  out.append (buf);
  out.append (".s>=");
  out.append (static_cast<const class BufferType*>(myBuffer->getType ())
	      ->getSize ());
  if (myIndex) {
    out.append (" || ");
    if (ixvalue)
      out.append (ixvalue);
    else {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
    }
    out.append (">");
    out.append (buf);
    out.append (".s");
  }
  out.append (")\n");
  cexpr.compileError (indent + 2, errBuf);

  out.indent (indent);
  out.append (lvalue);
  out.append (".s=");
  out.append (buf);
  out.append (".s+1;\n");

  if (static_cast<const class BufferType*>(getType ())->isStack ()) {
    if (myIndex) {
      out.indent (indent);
      out.append ("memcpy (");
      out.append (lvalue);
      out.append (".a, ");
      out.append (buf);
      out.append (".a, ");
      if (ixvalue)
	out.append (ixvalue);
      else {
	assert (myIndex->getKind () == Expression::eConstant);
	static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
      }
      out.append (" * sizeof *");
      out.append (buf);
      out.append (".a);\n");
    }
    out.indent (indent);
    out.append ("memcpy (");
    out.append (lvalue);
    out.append (".a");
    if (myIndex) {
      out.append ("+");
      if (ixvalue)
	out.append (ixvalue);
      else {
	assert (myIndex->getKind () == Expression::eConstant);
	static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
      }
    }
    out.append ("+1, ");
    out.append (buf);
    out.append (".a");
    if (myIndex) {
      out.append ("+");
      if (ixvalue)
	out.append (ixvalue);
      else {
	assert (myIndex->getKind () == Expression::eConstant);
	static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
      }
    }
    out.append (", (");
    out.append (buf);
    out.append (".s");
    if (myIndex) {
      out.append ("-");
      if (ixvalue)
	out.append (ixvalue);
      else {
	assert (myIndex->getKind () == Expression::eConstant);
	static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
      }
    }
    out.append (") * sizeof *");
    out.append (buf);
    out.append (".a);\n");
    out.indent (indent);
    out.append (lvalue);
    out.append (".a[");
    if (ixvalue)
      out.append (ixvalue);
    else if (myIndex) {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
    }
    else
      out.append ("0");
    out.append ("]=");
    out.append (itvalue);
    out.append (";\n");
  }
  else if (myIndex) {
    out.indent (indent);
    out.append ("memcpy (");
    out.append (lvalue);
    out.append (".a, ");
    out.append (buf);
    out.append (".a, (");
    out.append (buf);
    out.append (".s-");
    if (ixvalue)
      out.append (ixvalue);
    else {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
    }
    out.append (") * sizeof *");
    out.append (buf);
    out.append (".a);\n");

    out.indent (indent);
    out.append ("memcpy (");
    out.append (lvalue);
    out.append (".a+");
    out.append (buf);
    out.append (".s");
    out.append ("-");
    if (ixvalue)
      out.append (ixvalue);
    else {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex) ->getValue ().compile (out);
    }
    out.append ("+1, ");
    out.append (buf);
    out.append (".a+");
    out.append (buf);
    out.append (".s");
    out.append ("-");
    if (ixvalue)
      out.append (ixvalue);
    else {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
    }
    out.append (", ");
    if (ixvalue)
      out.append (ixvalue);
    else {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
    }
    out.append (" * sizeof *");
    out.append (buf);
    out.append (".a);\n");
    out.indent (indent);
    out.append (lvalue);
    out.append (".a[");
    out.append (buf);
    out.append (".s-");
    if (ixvalue)
      out.append (ixvalue);
    else {
      assert (myIndex->getKind () == Expression::eConstant);
      static_cast<const class Constant*>(myIndex)->getValue ().compile (out);
    }
    out.append ("]=");
    out.append (itvalue);
    out.append (";\n");
  }
  else {
    out.indent (indent);
    out.append ("memcpy (");
    out.append (lvalue);
    out.append (".a, ");
    out.append (buf);
    out.append (".a, ");
    out.append (buf);
    out.append (".s * sizeof *");
    out.append (buf);
    out.append (".a);\n");
    out.indent (indent);
    out.append (lvalue);
    out.append (".a[");
    out.append (buf);
    out.append (".s]=");
    out.append (itvalue);
    out.append (";\n");
  }

  delete[] ixvalue;
  delete[] itvalue;
  delete[] buf;
  compileConstraint (cexpr, indent, lvalue);
}

#endif // EXPR_COMPILE

/** Determine whether an expression needs to be enclosed in parentheses
 * @param kind	kind of the expression
 * @return	whether parentheses are necessary
 */
inline static bool
needParentheses (enum Expression::Kind kind)
{
  switch (kind) {
  case Expression::eVariable:
  case Expression::eConstant:
  case Expression::eUndefined:
  case Expression::eStructComponent:
  case Expression::eUnionComponent:
  case Expression::eUnionType:
  case Expression::eVectorIndex:
  case Expression::eUnop:
  case Expression::eBufferUnop:
  case Expression::eTypecast:
  case Expression::eCardinality:
  case Expression::eNot:
  case Expression::eRelop:
  case Expression::eStruct:
  case Expression::eVector:
  case Expression::eBuffer:
  case Expression::eBufferIndex:
  case Expression::eSet:
  case Expression::eTemporalBinop:
  case Expression::eTemporalUnop:
    return false;
  case Expression::eMarking:
  case Expression::ePlaceContents:
  case Expression::eSubmarking:
  case Expression::eMapping:
  case Expression::eEmptySet:
    assert (false);
  case Expression::eTransitionQualifier:
  case Expression::eBooleanBinop:
  case Expression::eBinop:
  case Expression::eIfThenElse:
  case Expression::eUnion:
  case Expression::eBufferWrite:
  case Expression::eBufferRemove:
  case Expression::eStructAssign:
  case Expression::eVectorAssign:
  case Expression::eVectorShift:
    break;
  }

  return true;
}

void
BufferWrite::display (const class Printer& printer) const
{
  const char* cast = myItem->getType ()->getName ();
  switch (myItem->getType ()->getKind ()) {
  case Type::tBool:
  case Type::tChar:
  case Type::tInt:
  case Type::tCard:
    cast = 0;
  default:
    break;
  }

  if (myIndex) {
    printer.delimiter ('(')++;
    myBuffer->display (printer);
    --printer.delimiter (')');
    printer.delimiter ('[')++;
    myIndex->display (printer);
    --printer.delimiter (']');
  }
  else
    myBuffer->display (printer);
  printer.delimiter ('+');

  if (cast) {
    printer.printRaw ("is");
    printer.delimiter (' ');
    printer.print (cast);
    printer.delimiter (' ');
  }

  if (::needParentheses (myItem->getKind ())) {
    printer.delimiter ('(')++;
    myItem->display (printer);
    --printer.delimiter (')');
  }
  else
    myItem->display (printer);
}
