/**CFile***********************************************************************

  FileName    [compileEval.c]

  PackageName [compile]

  Synopsis    [Performs compilation of expression into ADD.]

  Description [This function contains the code to compile an
  expression into ADD. This is done by a traversal of the expression.]

  SeeAlso     [compileUtil.c]

  Author      [Marco Roveri]

  Copyright   [ Copyright (c) 1998 by ITC-IRST and Carnegie Mellon
  University.  All Rights Reserved.  This software is for educational
  purposes only.  Permission is given to use, copy, modify, and
  distribute this software and its documentation provided that this
  introductory message is not removed and no monies are exchanged. No
  guarantee is expressed or implied by the distribution of this code.
  Send bug-reports and/or questions to: nusmv@irst.itc.it ]

******************************************************************************/

#include "compileInt.h" 

static char rcsid[] UTIL_UNUSED = "$Id: $";

/*---------------------------------------------------------------------------*/
/* Constant declarations                                                     */
/*---------------------------------------------------------------------------*/

/**Macro***********************************************************************

  Synopsis     [Return value in case an error occurs.]

  Description  [Return value in case an error occurs.]

  SeeAlso      [eval_struct_recur]

  SideEffects  []

******************************************************************************/
#define TYPE_ERROR ((node_ptr)(-1))

/**Macro***********************************************************************

  Synopsis     [Return value used to indicate that the evaluation of
  an atom is not yet terminated.]

  Description  [Return value used to indicate that the evaluation of
  an atom is not yet terminated and a reference to the evaluated
  symbols occurs.]

  SeeAlso      [get_definition]

  SideEffects  []

******************************************************************************/
#define EVALUATING ((add_ptr)(-1))

/*---------------------------------------------------------------------------*/
/* Type declarations                                                         */
/*---------------------------------------------------------------------------*/
typedef int (*INTPFII)(int, int);
typedef add_ptr (*ADDPFDA)(DdManager *, add_ptr);
typedef add_ptr (*ADDPFDAA)(DdManager *, add_ptr, add_ptr);
typedef add_ptr (*ADDPFDAII)(DdManager *, add_ptr, int, int);
typedef add_ptr (*ADDPFDAAII)(DdManager *, add_ptr, add_ptr, int, int);

/*---------------------------------------------------------------------------*/
/* Variable declarations                                                     */
/*---------------------------------------------------------------------------*/

/**Variable********************************************************************

  Synopsis    [Control the behavior of the function <code>eval</code>]

  Description [This variable is used to control the behavior of the
  function <code>eval</code> (specifically the behavior of its subroutine
  <code>get_definition</code>).] 

  SeeAlso     [eval get_definition]

******************************************************************************/
static int enforce_constant;

/**Variable********************************************************************

  Synopsis    [Modifies the behavior of the evaluator, depending the
  kind of expression we need to compile.]

  Description [Modifies the behavior of the evaluator, depending the
  kind of expression we need to compile. If we are interested in
  compiling the initial states we want ignore the TRANS assignments
  and normal assignments, and so on...]

  SeeAlso     [optional]

******************************************************************************/
static int assignment_type;
void set_assignment_type_init()  { assignment_type = INIT; }
void set_assignment_type_trans() { assignment_type = TRANS; }
void set_assignment_type_assign(){ assignment_type = ASSIGN;}

/**Variable********************************************************************

  Synopsis    [Variable used to store the range.]

  Description [Variable used to store the range of the currently
  evaluated variable. It is used for error checking and reporting.]

  SeeAlso     [the_var]

******************************************************************************/
static node_ptr the_range = Nil;
static node_ptr get_the_range(void) {return(the_range);}
static void set_the_range(node_ptr range) {the_range = range;}

/**Variable********************************************************************

  Synopsis    [Variable used to store the variable name.]

  Description [Variable used to store the name of the currently
  evaluated variable. It is used for error checking and reporting.]

  SeeAlso     [the_range]

******************************************************************************/
node_ptr the_var;
static node_ptr get_the_var(void) {return(the_var);}
static void set_the_var(node_ptr v) {the_var = v;}

/**Variable********************************************************************

  Synopsis    [Hash to cache result of <code>DEFINE</code> evaluation.]

  Description [This hash is used by <code>get_definition</code> to
  cache the result of <code>DEFINE</code> evaluation, and to check for
  recursive definition of defined symbols.]

  SeeAlso     []

******************************************************************************/
static hash_ptr value_hash;
void init_value_hash() { value_hash = new_assoc(); }
void insert_value_hash(node_ptr x, node_ptr y) { insert_assoc(value_hash, x, y);}
node_ptr lookup_value_hash(node_ptr x) {return(find_assoc(value_hash, x));}
static assoc_retval value_hash_free(char *key, char *data, char * arg) {
  add_ptr element = (add_ptr)data;

  if ((element != (add_ptr)NULL) && (element != EVALUATING)) {
    add_free(dd_manager, element);
  }
  return(ASSOC_DELETE);
}
void clear_value_hash() {clear_assoc_and_free_entries(value_hash, value_hash_free);}

/*---------------------------------------------------------------------------*/
/* Static function prototypes                                                */
/*---------------------------------------------------------------------------*/
static int plus_op ARGS((int a, int b));
static int minus_op ARGS((int a, int b));
static int times_op ARGS((int a, int b));
static int divide_op ARGS((int a, int b));
static int mod_op ARGS((int a, int b));
static int lt_op ARGS((int a, int b));
static int gt_op ARGS((int a, int b));
static node_ptr numeric_op ARGS((INTPFII op, node_ptr n1, node_ptr n2));
static node_ptr node_minus ARGS((node_ptr n1, node_ptr n2));
static node_ptr node_times ARGS((node_ptr n1, node_ptr n2));
static node_ptr node_divide ARGS((node_ptr n1, node_ptr n2));
static node_ptr node_mod ARGS((node_ptr n1, node_ptr n2));
static node_ptr node_lt ARGS((node_ptr n1, node_ptr n2));
static node_ptr node_gt ARGS((node_ptr n1, node_ptr n2));
static node_ptr node_union ARGS((node_ptr n1, node_ptr n2));
static add_ptr add_plus ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_minus ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_times ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_divide ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_mod ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_lt ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_gt ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr add_union ARGS((DdManager * dd, add_ptr a, add_ptr b));
static add_ptr eval_sign ARGS((add_ptr a, int flag));
static add_ptr unary_op ARGS((DdManager * dd, ADDPFDA op, node_ptr n, int resflag, int argflag, node_ptr context));
static add_ptr binary_op ARGS((DdManager * dd, ADDPFDAA op, node_ptr n, int resflag, int argflag1,int argflag2, node_ptr context));
static add_ptr ternary_op ARGS((DdManager * dd, ADDPFDAII op, node_ptr n, int resflag, int argflag, node_ptr context));
static add_ptr quaternary_op ARGS((DdManager * dd, ADDPFDAAII op, node_ptr n, int resflag, int argflag1, int argflag2, node_ptr context));
static add_ptr if_then_else_op ARGS((DdManager * dd, node_ptr node, node_ptr context));
static node_ptr eval_struct_recur ARGS((node_ptr n, node_ptr context));
static void range_check ARGS((node_ptr n));
static add_ptr get_definition ARGS((node_ptr n));
static add_ptr eval_recur ARGS((node_ptr n, node_ptr context));

/* Yuan Lu */
static node_ptr eval_big_case ARGS((node_ptr, node_ptr));
static node_ptr eval_big_expr_recur ARGS((node_ptr, node_ptr));
static node_ptr binary_arithmetic_op ARGS((ADDPFDAA, node_ptr, node_ptr));
static node_ptr arithmetic_op_recur ARGS((ADDPFDAA, node_ptr, node_ptr, node_ptr));
static void add_free_list ARGS((node_ptr));

/*---------------------------------------------------------------------------*/
/* Definition of exported functions                                          */
/*---------------------------------------------------------------------------*/
 
/**Function********************************************************************

  Synopsis           [Given an expression the corresponding ADD is
  returned back.]

  Description        [This function takes an expression in input and
  gives as output the corresponding ADD.<p>
  This function if receives in input a domain variables, it returns as
  its evaluation the ADD representing its boolean encoding. It has as
  leaves the value associated to that path.<br>
  For instance consider the declaration:<br>
  <code>VAR x : 1..6;</code><br>
  it is encoded with three boolean variables as below:
  <pre>

              x1
              /\\ 
            1/  \\0
            /    \\
           /      \\
          x2       x2
         /\\        / \\
        /  \\       |  \\
      x3    \\      x3  \\  
     /  \\    \\    /  \\  \\ 
    /    \\   |   /    \\  \\
    1    5   3   2    6   4

  </pre>
  If the expression is complex, then it recursively apply to the
  operands, and then apply the operation to the operands, returning
  the resulting ADD.]

  SideEffects        []

  SeeAlso            [eval_recur]

******************************************************************************/
add_ptr eval(node_ptr expr, node_ptr context)
{
  add_ptr res;
  int temp = yylineno;

  if (expr == Nil) return(add_one(dd_manager));
  yylineno = expr->lineno;
  res = eval_recur(expr, context);
  yylineno = temp;
  return(res);
}

/**Function********************************************************************

  Synopsis           [Given a list of expressions, this returns the list
  of the corresponding ADD.]

  Description        [This function takes as input a list of
  expressions, and returns as output the list of the corresponding
  ADD. For each element of the list a call to <code>eval</code> is performed.]

  SideEffects        []

  SeeAlso            [eval]

******************************************************************************/
node_ptr eval_tree(node_ptr nodes, node_ptr context)
{
  if (nodes == Nil) return(Nil);
  if (node_get_type(nodes) == CONS)
    return(find_node(CONS,eval_tree(nodes->left.nodetype,context),
		     eval_tree(nodes->right.nodetype,context)));
  return(find_node(BDD,(node_ptr)eval(nodes,context),Nil));
}

/**Function********************************************************************

  Synopsis           [Evaluates the expression given as input in the
  corresponding context and then simplifies the result using assumption.]

  Description        [This function takes as input an expression
  <code>n</code> evaluates it in context <code> context</code> and this
  partial result is then simplified using the ADD <code>assumption</code>.]

  SideEffects        []

  SeeAlso            [eval, add_simplify_assuming]

******************************************************************************/
add_ptr eval_simplify(node_ptr expr, node_ptr context, add_ptr assumption)
{
  if (expr == Nil) return(add_one(dd_manager));
  yylineno = expr->lineno;
  switch(node_get_type(expr)){
  case AND:
    {
      add_ptr tmp_1;
      add_ptr l, r, res;
      
      l = eval_simplify(car(expr), context,assumption);
      r = eval_simplify(cdr(expr), context,assumption);
      tmp_1 = add_and(dd_manager, l, r);
      
      res = add_simplify_assuming(dd_manager, tmp_1, assumption);

      /* free temporary results */
      add_free(dd_manager, l);
      add_free(dd_manager, r);
      add_free(dd_manager, tmp_1);

      return(res);
    }
  case CONTEXT:
    return(eval_simplify(cdr(expr), car(expr), assumption));
  default:
    return(eval(expr, context));
  }
}

/**Function********************************************************************

  Synopsis           [Takes an expression representing an identifier
  and recursively evaluate it.]

  Description        [Takes an expression representing an identifier
  and recursively evaluate it. Explain better....]

  SideEffects        []

  SeeAlso            [eval_struct_recur]

******************************************************************************/
node_ptr eval_struct(node_ptr n, node_ptr context)
{
  node_ptr res;
  int temp = yylineno;

  if (n == Nil) return(Nil);
  yylineno = n->lineno;
  res = eval_struct_recur(n, context);
  yylineno = temp;
  return(res);
}


/**Function********************************************************************

  Synopsis           [Evaluates a number in a context.]

  Description        [Evaluate the <em>NUMBER</em> represented by <code>e</code> in
  context <code>context</code>. <em>NUMBERS</em> can be encoded in
  different ways in different processes.]

  SideEffects        []

  SeeAlso            [enforce_constant eval]

******************************************************************************/
int eval_num(node_ptr e, node_ptr context)
{
  node_ptr n;
  add_ptr d;
  extern int enforce_constant;
  int temp = enforce_constant;

  enforce_constant = 1;
  d = eval(e, context);
  enforce_constant = temp;
  if (!add_isleaf(d)) internal_error("eval_num: !add_isleaf(d)");
  n = add_get_leaf(dd_manager, d);
  add_free(dd_manager, d);
  if (node_get_type(n) != NUMBER) rpterr("numeric constant required");
  enforce_constant = temp;
  return(n->left.inttype);
}


/**Function********************************************************************

  Synopsis           [Returns the definition of a symbol, if defined
  else report an error.]

  Description        [Returns the ADD of the definition of symbol
  <code>n</code> if defined, else prints an error message and then
  exits if you are not in the interactive shell.]

  SideEffects        []

  SeeAlso            [get_definition]

******************************************************************************/
add_ptr enforce_definition(node_ptr n)
{
  add_ptr res;

  /* get_definition already reference the result. */
  res = get_definition(n);
  if (res == (add_ptr)NULL) error_undefined(n);
  return(res);
}

/*---------------------------------------------------------------------------*/
/* Definition of static functions                                            */
/*---------------------------------------------------------------------------*/

/**Function********************************************************************

  Synopsis           [Adds two integers.]

  Description        [Adds two integers]

  SideEffects        []

******************************************************************************/
static int plus_op(int a, int b) { return(a+b); }

/**Function********************************************************************

  Synopsis           [Subtracts two integers]

  Description        [Subtracts two integers]

  SideEffects        []

******************************************************************************/
static int minus_op(int a, int b) { return(a-b); }

/**Function********************************************************************

  Synopsis           [Multiplies two integers]

  Description        [Multiplies two integers]

  SideEffects        []

******************************************************************************/
static int times_op(int a, int b) { return(a*b); }

/**Function********************************************************************

  Synopsis           [Divide two integers]

  Description        [Divide two integers, if a division by zero is
  performed, then an error occurs.]

  SideEffects        [required]

******************************************************************************/
static int divide_op(int a, int b)
{
  int r;
  if ( b == 0 ) division_by_zero();
  r = a%b;
  if (r<0) return(a/b-1);
  return(a/b);
}

/**Function********************************************************************

  Synopsis           [Computes the modulo of the division of two integers]

  Description        [Computes the modulo of the division of two
  integers, if a division by zero is performed, then an error occurs.]

  SideEffects        []

******************************************************************************/
static int mod_op(int a, int b)
{
  int r;
  if ( b == 0 ) division_by_zero();
  r = a%b;
  if (r<0) r += b;
  return(r);
}

/**Function********************************************************************

  Synopsis           [Checks if an integer is less then the other.]

  Description        [Checks if an integer is less then the other.]

  SideEffects        []

******************************************************************************/
static int lt_op(int a, int b)
{
  if (a < b) return(1);
  return(0);
}

/**Function********************************************************************

  Synopsis           [Checks if an integer is greater then the other.]

  Description        [Checks if an integer is greater then the other.]

  SideEffects        []

******************************************************************************/
static int gt_op(int a, int b)
{
  if (a > b) return(1);
  return(0);
}

/**Function********************************************************************

  Synopsis           [Applies generic function to two operands.]

  Description        [Applies generic function to two operands. The
  two operands have to be integers.]

  SideEffects        []

******************************************************************************/
static node_ptr numeric_op(INTPFII op, node_ptr n1, node_ptr n2)
{
  if(node_get_type(n1) != NUMBER)error_not_a_number(n1);
  if(node_get_type(n2) != NUMBER)error_not_a_number(n2);
  return(find_node(NUMBER,(node_ptr)(*op)((int)car(n1),(int)car(n2)),Nil));
}


/**Function********************************************************************

  Synopsis           [Adds two integer nodes.]

  Description        [Adds two integer nodes.]

  SideEffects        []

******************************************************************************/
node_ptr node_plus(node_ptr n1, node_ptr n2)
{ return(numeric_op(plus_op,n1,n2)); } /* No static because used in interactive loop. */

/**Function********************************************************************

  Synopsis           [Adds 1 to an integer node.]

  Description        [Adds 1 to an integer node.]

  SideEffects        []

******************************************************************************/
node_ptr node_plus1(node_ptr n1)
{ return(numeric_op(plus_op,n1,one_number)); }
/* The above function is not static because used in interactive loop. */

/**Function********************************************************************

  Synopsis           [Subtracts two integer nodes.]

  Description        [Subtracts two integer nodes.]

  SideEffects        []

******************************************************************************/
static node_ptr node_minus(node_ptr n1, node_ptr n2)
{ return(numeric_op(minus_op,n1,n2)); }

/**Function********************************************************************

  Synopsis           [Multiplies two integer nodes.]

  Description        [Multiplies two integer nodes.]

  SideEffects        []

******************************************************************************/
static node_ptr node_times(node_ptr n1, node_ptr n2)
{ return(numeric_op(times_op,n1,n2)); }

/**Function********************************************************************

  Synopsis           [Divides two integer nodes.]

  Description        [Divides two integer nodes.]

  SideEffects        []

******************************************************************************/
static node_ptr node_divide(node_ptr n1, node_ptr n2)
{ return(numeric_op(divide_op,n1,n2)); }

/**Function********************************************************************

  Synopsis           [Computes the modulo of the division between two
  integer nodes.]

  Description        [Computes the modulo of the division between two
  integer nodes.]

  SideEffects        []

******************************************************************************/
static node_ptr node_mod(node_ptr n1, node_ptr n2)
{ return(numeric_op(mod_op,n1,n2)); }

/**Function********************************************************************

  Synopsis           [Checks if an integer node is less then the other.]

  Description        [Checks if an integer node is less then the other.]

  SideEffects        []

******************************************************************************/
static node_ptr node_lt(node_ptr n1, node_ptr n2)
{ return(numeric_op(lt_op,n1,n2)); }

/**Function********************************************************************

  Synopsis           [Checks if an integer node is greater then the other.]

  Description        [Checks if an integer node is greater then the other.]

  SideEffects        []

******************************************************************************/
static node_ptr node_gt(node_ptr n1, node_ptr n2)
{ return(numeric_op(gt_op,n1,n2)); }

/**Function********************************************************************

  Synopsis           [Computes the set union of two s_expr.]

  Description        [This function computes the sexp resulting from
  the union of s_expr "n1" and "n2".]

  SideEffects        []

******************************************************************************/
static node_ptr node_union(node_ptr n1, node_ptr n2)
{
  if(n1 != Nil && node_get_type(n1) != CONS) n1 = find_node(CONS, n1, Nil);
  if(n2 != Nil && node_get_type(n2) != CONS) n2 = find_node(CONS, n2, Nil);
  if(n1 == Nil) return(n2);
  if(n2 == Nil) return(n1);
  if(car(n1) == car(n2))
    return(find_node(CONS, car(n1), node_union(cdr(n1), cdr(n2))));
  if(((int)car(n1)) < ((int)car(n2)))
    return(find_node(CONS, car(n1), node_union(cdr(n1), n2)));
  return(find_node(CONS, car(n2), node_union(n1, cdr(n2))));
}

/**Function********************************************************************

  Synopsis           [Set inclusion]

  Description        [Checks if s_expr "n1" is a subset of s_expr
  "n2", if it is the case then <code>one_number</code> is returned,
  else <code>zero_number</code> is returned.]

  SideEffects        []

  SeeAlso            [node_equal]

******************************************************************************/
node_ptr node_setin(node_ptr n1, node_ptr n2)
{
  if(n2 == Nil) return(zero_number);
  if(node_get_type(n2) != CONS){
    if(n1 == n2) return(one_number);
    return(zero_number);
  }
  if(n1 == car(n2)) return(one_number);
  return(node_setin(n1,cdr(n2)));
}

/**Function********************************************************************

  Synopsis           [Checks for node equality.]

  Description        [This function checks if symbols <code>n1</code> and
  <code>n2</code> are equal, if it is then it returns the symbol
  <code>1</code> (that's <code>one_number</code>) else the symbol <code>0</code>
  (that's <code>zero_number</code>).]

  SideEffects        []

  SeeAlso            [node_setin]

******************************************************************************/
node_ptr node_equal(node_ptr n1, node_ptr n2)
{
  if(n1 == n2) return(one_number);
  return(zero_number);
}

/**Function********************************************************************

  Synopsis           [Adds two integer ADDs]

  Description        [Adds two integer ADDs.]

  SideEffects        []

******************************************************************************/
static add_ptr add_plus(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_plus,a,b)); }

/**Function********************************************************************

  Synopsis           [Subtracts two integer ADDs]

  Description        [Subtracts two integer ADDs.]

  SideEffects        []

******************************************************************************/
static add_ptr add_minus(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_minus,a,b)); }

/**Function********************************************************************

  Synopsis           [Multiplies two integer ADDs]

  Description        [Multiplies two integer ADDs]

  SideEffects        []

******************************************************************************/
static add_ptr add_times(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_times,a,b)); }

/**Function********************************************************************

  Synopsis           [Divides two integer ADDs]

  Description        [Divides two integer ADDs]

  SideEffects        []

******************************************************************************/
static add_ptr add_divide(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_divide,a,b)); }

/**Function********************************************************************

  Synopsis           [Computes the modulo of the integer division of
  two integer ADDs.]

  Description        [Computes the modulo of the integer division of
  two integer ADDs.]

  SideEffects        []

******************************************************************************/
static add_ptr add_mod(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_mod,a,b)); }

/**Function********************************************************************

  Synopsis           [Checks if two integer ADDs are in the less then relation.]

  Description        [Checks if two integer ADDs are in the less then relation.]

  SideEffects        []

******************************************************************************/
static add_ptr add_lt(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_lt,a,b)); }

/**Function********************************************************************

  Synopsis           [Checks if two integer ADDs are in the less then relation.]

  Description        [Checks if two integer ADDs are in the less then relation.]

  SideEffects        []

******************************************************************************/
static add_ptr add_gt(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_gt,a,b)); }

/**Function********************************************************************

  Synopsis           [Computes the set union of two set ADDs.]

  Description        [Computes the set union of two set ADDs.]

  SideEffects        []

******************************************************************************/
static add_ptr add_union(DdManager * dd, add_ptr a, add_ptr b)
{ return(add_apply(dd,node_union,a,b)); }

/**Function********************************************************************

  Synopsis           [Complements an ADD according to a flag.]

  Description        [Given the ADD <code>a</code>, this function returns
  the negation of ADD <code>a</code> or <code>a</code> itself according the
  value of <code>flag</code>. If <code>flag = -1</code> then returns <code>not
  a</code>, else returns <code>a</code>. It is important that the ADD is a
  zero/one ADD (i.e. it has only zero or one as leaf).]

  SideEffects        []

  SeeAlso            [eval]

******************************************************************************/
static add_ptr eval_sign(add_ptr a, int flag)
{
  if (flag == -1 ) return(add_not(dd_manager, a));
  else {
    add_ref(a);
    return(a);
  }
}

/**Function********************************************************************

  Synopsis           [Applies unary operation.]

  Description        [Takes in input the expression <code>n</code> and a
  unary operation <code>op</code>. Evaluates <code>n</n> and applies to this
  partial result the unary operator <code>op</code>. The sign of the
  partial result and of the result depends respectively from the flag
  <code>argflag</code> and <code>resflag</code>.] 

  SideEffects        []

  SeeAlso            [eval, binary_op, ternary_op, quaternary_op]

******************************************************************************/
static add_ptr unary_op(DdManager * dd, ADDPFDA op, node_ptr n,
                        int resflag, int argflag, node_ptr context)
{
  add_ptr tmp_1, tmp_2, res;
  add_ptr arg = eval(car(n), context);

  set_the_node(n);

  /* compute and ref argument of operation according its sign */
  tmp_1 = eval_sign(arg, argflag);

  /* apply and ref the result of the application of "op" to previous arg. */
  tmp_2 = op(dd, tmp_1);

  /* compute and ref the result according to sign of the result */
  res = eval_sign(tmp_2, resflag);

  /* free temporary results */
  add_free(dd, arg);
  add_free(dd, tmp_1);
  add_free(dd, tmp_2);

  return(res);
}

/**Function********************************************************************

  Synopsis           [Applies binary operation.]

  Description        [Takes in input the expression <code>n</code> and a
  binary operation <code>op</code>. Extracts from <code>n</n> the operands
  and evaluates them. The binary operator <code>op</code> is then applied
  to these partial results. The sign of the partial results and of the
  result depends respectively from the flags <code>argflag1</code>,
  <code>argflag2</code> and <code>resflag</code>.]

  SideEffects        []

  SeeAlso            [eval, unary_op, ternary_op, quaternary_op]

******************************************************************************/
static add_ptr binary_op(DdManager * dd, ADDPFDAA op, node_ptr n,
                         int resflag, int argflag1,int argflag2, node_ptr context)
{
  add_ptr tmp_1, tmp_2, tmp_3, res;
  add_ptr arg1 = eval(car(n), context);
  add_ptr arg2 = eval(cdr(n), context);

  set_the_node(n);

  tmp_1 = eval_sign(arg1, argflag1);
  tmp_2 = eval_sign(arg2, argflag2);
  tmp_3 = op(dd, tmp_1, tmp_2);
  res = eval_sign(tmp_3, resflag);

  add_free(dd, arg1);
  add_free(dd, arg2);
  add_free(dd, tmp_1);
  add_free(dd, tmp_2);
  add_free(dd, tmp_3);
  
  return(res);
}

/**Function********************************************************************

  Synopsis           [Applies ternary operation.]

  Description        [Takes in input the expression <code>n</code> and a
  ternary operation <code>op</code>. Extracts from <code>n</n> the operands
  and evaluates them.<br>
  The second and third arguments have to evaluate to numbers. And
  <code>op</code> is a function that takes as input an ADD an two integers.
  The ternary operator <code>op</code> is then applied to these partial
  results. The sign of the partial result and of the result depends
  respectively from the flags <code>argflag</code> and <code>resflag</code>.]

  SideEffects        []

  SeeAlso            [eval, unary_op, binary_op, quaternary_op]

******************************************************************************/
static add_ptr ternary_op(DdManager * dd, ADDPFDAII op, node_ptr n,
                          int resflag, int argflag, node_ptr context)
{
  add_ptr tmp_1, tmp_2, res;
  add_ptr arg1 = eval(car(n), context);
  int arg2 = eval_num(car(cdr(n)), context);
  int arg3 = eval_num(cdr(cdr(n)), context);

  set_the_node(n);

  tmp_1 = eval_sign(arg1, argflag);
  tmp_2 = op(dd, tmp_1, arg2, arg3);
  res = eval_sign(tmp_2, resflag);

  add_free(dd, arg1);
  add_free(dd, tmp_1);
  add_free(dd, tmp_2);

  return(res);
}

/**Function********************************************************************

  Synopsis           [Applies quaternary operation.]

  Description        [Takes in input the expression <code>n</code> and a
  quaternary operation <code>op</code>. Extracts from <code>n</n> the operands
  and evaluates them.<br>
  The third and fourth arguments have to evaluate to numbers. And
  <code>op</code> is a function that takes as input two ADD and two integers.
  The quaternary operator <code>op</code> is then applied to these partial
  results. The sign of the partial result and of the result depends
  respectively from the flags <code>argflag1</code>, <code>argflag2</code> and
  <code>resflag</code>.]

  SideEffects        []

  SeeAlso            [eval, unary_op, binary_op, ternary_op]

******************************************************************************/
static add_ptr quaternary_op(DdManager * dd, ADDPFDAAII op, node_ptr n,
                             int resflag, int argflag1, int argflag2, node_ptr context)
{
  add_ptr tmp_1, tmp_2, tmp_3, res;
  add_ptr arg1 = eval(car(car(n)), context);
  add_ptr arg2 = eval(cdr(car(n)), context);
  int arg3 = eval_num(car(cdr(n)), context);
  int arg4 = eval_num(cdr(cdr(n)), context);

  set_the_node(n);

  tmp_1 = eval_sign(arg1, argflag1);
  tmp_2 = eval_sign(arg2, argflag1);
  tmp_3 = op(dd, tmp_1, tmp_2, arg3, arg4);
  res = eval_sign(res, resflag);

  add_free(dd, arg1);
  add_free(dd, arg2);
  add_free(dd, tmp_1);
  add_free(dd, tmp_2);
  add_free(dd, tmp_3);

  return(res);
}

/**Function********************************************************************

  Synopsis           [Evaluates if_then_else expressions returning
  the ADD representing <em>IF ifarg THEN thenarg ELSE elsarg</em>.]

  Description        [Evaluates if_then_else expressions returning the
  ADD representing <em>IF ifarg THEN thenarg ELSE elsarg</em>, where
  <code>ifarg</code>, <code>thenarg</code>, <code>elsearg</code> are the ADD
  obtained by evaluating <code>ifexp</code>, <code>thenexp</code>,
  <code>elseexp</code> respectively in context <code>context</code>. The
  resulting ADD is saved in the ADD hash before returning it to the
  calling function.]

  SideEffects        []

  SeeAlso            [add_ifthenelse]

******************************************************************************/
static add_ptr if_then_else_op(DdManager * dd, node_ptr node, node_ptr context)
{
  add_ptr res;
  add_ptr ifarg = eval(car(car(node)), context);
  add_ptr thenarg = eval(cdr(car(node)), context);
  add_ptr elsearg = eval(cdr(node), context);

  res = add_ifthenelse(dd, ifarg, thenarg, elsearg);
  
  /* free temporary results */
  add_free(dd, ifarg);
  add_free(dd, thenarg);
  add_free(dd, elsearg);

  return(res);
}

/**Function********************************************************************

  Synopsis           [Performs the recursive step of <code>eval_struct</code>.]

  Description        [Performs the recursive step of <code>eval_struct</code>.]

  SideEffects        []

  SeeAlso            [eval_struct]

******************************************************************************/
static node_ptr eval_struct_recur(node_ptr n, node_ptr context)
{
  node_ptr temp,name;

  switch(node_get_type(n)){
  case CONTEXT: return(eval_struct(cdr(n), car(n)));
  case ATOM:
    name = find_node(DOT, context, find_atom(n));
    if ((temp = lookup_param_hash(name)))
      return(eval_struct(temp, context));
    return(name);
  case DOT:
    temp = eval_struct(car(n), context);
    if (temp == TYPE_ERROR) rpterr("type error, operator = .");
    return(find_node(DOT, temp, find_atom(cdr(n))));
  case ARRAY:
    temp = eval_struct(car(n), context);
    if (temp == TYPE_ERROR) rpterr("type error, operator = []");
    return(find_node(ARRAY, temp,
                     find_node(NUMBER, (node_ptr)eval_num(cdr(n), context), Nil)));
  case SELF:
    return(context);
  default:
      return(TYPE_ERROR);
  }
}

/**Function********************************************************************

  Synopsis           [Checks if the values of <code>n</code> is in the
  range allowed for the variable.]

  Description        [Checks if the values of <code>n</code> is in the
  range allowed for the variable. The allowed values are stored in the
  global variable <code>the_range</code>, which is updated each time this
  function is called with the range allowed for the variable to be
  checked. If the value is not in the range, then error occurs.]

  SideEffects        []

******************************************************************************/
static void range_check(node_ptr n)
{
  if (n == Nil) internal_error("range_check: n == Nil");
  if (node_get_type(n) == CONS) {
    while(n) {
      if (!in_list(car(n), get_the_range())) range_error(car(n), get_the_var());
      n = cdr(n);
    }
  }
  else
    if (!in_list(n, get_the_range())) range_error(n, get_the_var());
}

/**Function********************************************************************

  Synopsis           [Given a symbol, the corresponding ADD is returned.]

  Description        [Given the symbol represented by <code>n</code>, this
  function returns the ADD of its definition.]

  SideEffects        []

  SeeAlso            [eval]

******************************************************************************/
static add_ptr get_definition(node_ptr n)
{
  add_ptr res;
  node_ptr def = lookup_symbol_hash(n);

  if (!def) return((add_ptr)NULL);
  if (enforce_constant && (node_get_type(def) == VAR)) rpterr("constant required");
  if ((node_get_type(def) == BDD) || (node_get_type(def) == VAR)) {
    res = (add_ptr)car(def);
    add_ref(res);
    return(res);
  }
  /* It is a DEFINE, the type is CONTEXT */
  res = (add_ptr)lookup_value_hash(n);
  if (res == EVALUATING) error_circular(n);
  if (res != NULL) {
    add_ref(res);
    return(res);
  }
  if (opt_verbose_level_gt(options, 1)){
    inc_indent_size();
    indent_node(nusmv_stderr, "evaluating ", n, ":\n");
  }
  insert_value_hash(n, (node_ptr)EVALUATING);
  io_atom_push(n);
  res = eval(def, Nil);
  io_atom_pop();
  /*
    We need to reference the returned value because we memoize the
    result in the value_hash, and the calling routines could free the
    returned value and invalidate the contents of the hashed value.
  */
  add_ref(res); 
  insert_value_hash(n, (node_ptr)res);
  if (opt_verbose_level_gt(options, 1)){
    indent_node(nusmv_stderr, "size of ", n, " = ");
    fprintf(nusmv_stderr, "%d ADD nodes\n", add_size(dd_manager, res));
    dec_indent_size();
  }
  return(res);
}

/**Function********************************************************************

  Synopsis           [Performs the recursive step of the <code>eval</code> function.]

  Description        [The expression <code>n</code> is recursively
  compiled in ADD:
  <ul>
  <li> If it is an <em>ATOM</em>:
      <ul>
      <li> If it is a program parameter, then its actual name is
           extracted from the parameter hash, and it is evaluated. The
           result of evaluation is returned.</li> 
      <li> If it is a constant, then the corresponding ADD is returned
           back.</li>
      </ul></li>
  <li> If it is a binary operator, then the operands are evaluated,
       and then the binary operator is applied to the operands.</li>
  <li> If it is an <em>EQDEF</em>, i.e. some kind of equality, the
       behavior depends on the value of the variable
       <code>assignment_type</code>, which can be either <em>INIT</em>
       (assignment of initial states), <em>TRANS</em> (assignment of
       next variables), and <em>ASSIGN</em> (normal assignments).</li>
  </ul>
  ]

  SideEffects        []

  SeeAlso            [eval get_definition]

******************************************************************************/
static add_ptr eval_recur(node_ptr expr, node_ptr context)
{
  if (expr == Nil) return(add_one(dd_manager));
  switch(node_get_type(expr)) {
  case ATOM: {
    node_ptr name  = find_node(DOT, context, find_atom(expr));
    node_ptr temp1 = lookup_param_hash(name);
    node_ptr temp2 = lookup_symbol_hash(name);
    add_ptr  temp3 = (add_ptr)lookup_constant_hash(find_atom(expr));
    
    /*
      if the atom belongs to any combination of parameters, symbols
      or constants an error occurred.
    */
    if ((temp1 && temp2) || (temp2 && temp3) || (temp3 && temp1))
      rpterr("atom \"%s\" is ambiguous", str_get_text(node_get_lstring(expr)));
    /* the atom is a parameter */
    if (temp1) return(eval(temp1, context));
    /* the atom is a constant */
    if (temp3) {
      add_ref(temp3);
      return(temp3);
    }
  } /* fall through on purpose here */
  case DOT:
  case ARRAY:    return(enforce_definition(eval_struct(expr,  context)));
  case CONTEXT:  return(eval(cdr(expr), car(expr)));
  case CONS:
  case AND:      return(binary_op(dd_manager, add_and, expr, 1, 1, 1, context));
  case OR:       return(binary_op(dd_manager, add_or, expr, 1, 1, 1, context));
  case NOT:      return(unary_op(dd_manager, add_not, expr, 1, 1, context));
  case IMPLIES:  return(binary_op(dd_manager, add_or, expr, 1, -1, 1, context));
  case IFF:      return(binary_op(dd_manager, add_xor, expr, -1, 1, 1, context));
  case CASE:     return(if_then_else_op(dd_manager, expr, context));
  case EQUAL:    return(binary_op(dd_manager, add_equal, expr, 1, 1, 1, context));
  case PLUS:     return(binary_op(dd_manager, add_plus, expr, 1, 1, 1, context));
  case MINUS:    return(binary_op(dd_manager, add_minus, expr, 1, 1, 1, context));
  case TIMES:    return(binary_op(dd_manager, add_times, expr, 1, 1, 1, context));
  case DIVIDE:   return(binary_op(dd_manager, add_divide, expr, 1, 1, 1, context));
  case MOD:      return(binary_op(dd_manager, add_mod, expr, 1, 1, 1, context));
  case LT:       return(binary_op(dd_manager, add_lt, expr, 1, 1, 1, context));
  case GT:       return(binary_op(dd_manager, add_gt, expr, 1, 1, 1, context));
  case LE:       return(binary_op(dd_manager, add_gt, expr, -1, 1, 1, context));
  case GE:       return(binary_op(dd_manager, add_lt, expr, -1, 1, 1, context));
  case TRUEEXP:  return(add_one(dd_manager));
  case FALSEEXP: return(add_zero(dd_manager));
  case NUMBER:   return(add_leaf(dd_manager, find_atom(expr)));
  case NEXT:     return(unary_op(dd_manager, add_shift_forward, expr, 1, 1, context));
  case EQDEF:
    {
      node_ptr t1, t2, r;

      switch(assignment_type){
      case INIT: /* we deal wit statements of this kind: init(x) := init_expr */
	if (node_get_type(car(expr)) != SMALLINIT) return(add_one(dd_manager));
	t1 = t2 = car(car(expr)); /* (ATOM x Nil) */
	break;
      case TRANS: /* we deal wit statements of this kind: next(x) := next_expr */
	if (node_get_type(car(expr)) != NEXT) return(add_one(dd_manager));
	t1 = car(expr); /* (NEXT (ATOM x Nil) Nil) */
	t2 = car(car(expr)); /* (ATOM x Nil) */
	break;
      case ASSIGN: /* we deal wit statements of this kind: x := simple_expr */
	if (node_get_type(car(expr)) == NEXT || node_get_type(car(expr)) == SMALLINIT)
          return(add_one(dd_manager));
	t1 = t2 = car(expr); /* (ATOM x Nil) */
	break;
      default:
	rpterr("Unknown assignment type");
      }
      /* we contextualize "x" and we extract its informations */
      r = lookup_symbol_hash(eval_struct(t2, context));
      if (r == Nil) error_undefined(t2);
      if (node_get_type(r) != VAR) error_redefining(t2);
      if (opt_verbose_level_gt(options, 2)) {
	inc_indent_size();
	indent_node(nusmv_stderr, "evaluating ", t1, ":\n");
      }
      {
        node_ptr abs = abs_expr_pre;
	node_ptr assvar = eval_struct(t2, context);
	add_ptr  nexvar = eval(t1, context);
        add_ptr  v;

	for(;abs;abs=cdr(abs)) {
	  if(isamevar(assvar, car(car(abs)))) break;
	}
        if(abs && node_get_type(cdr(expr))==CASE) {
	  node_ptr list = eval_big_case(cdr(expr), context);
	  bdd_ptr res = bdd_zero(dd_manager);
	  for(;list;list=cdr(cdr(list))) {
	    add_ptr conda = (add_ptr) car(list);
	    add_ptr val = (add_ptr) car(cdr(list));
	    bdd_ptr condb = add_to_bdd(dd_manager, conda);
	    bdd_ptr c_b_p = Abs_AbsAbstractFunc(condb, abs_expr_pre);

	    add_ptr rel1 = add_equal(dd_manager, nexvar, val);
	    bdd_ptr rel2 = add_to_bdd(dd_manager, rel1);
	    bdd_ptr rel3 = Abs_AbsAbstractFunc(rel2, abs_expr_pre);
	    bdd_and_accumulate(dd_manager, &rel3, c_b_p);
	    bdd_or_accumulate(dd_manager, &res, rel3);

	    add_free(dd_manager, conda);
	    add_free(dd_manager, val);
	    add_free(dd_manager, rel1);

	    bdd_free(dd_manager, condb);
	    bdd_free(dd_manager, c_b_p);
	    bdd_free(dd_manager, rel2);
	    bdd_free(dd_manager, rel3);
	  }
	  v = bdd_to_add(dd_manager, res); bdd_free(dd_manager, res);
        } else {
          /* We evaluate the right hand side of the assignment (lhs := rhs) */
	  v = eval(cdr(expr), context);

          /* We check that the value of the rhs are admissible values for the lhs */
	  set_the_var(t2);
	  set_the_range(cdr(r));
	  add_walkleaves(range_check, v);
          /* We perform set inclusion of the evaluation of the rhs with the lhs */
          {
            add_ptr v1 = eval(t1, context);
            add_ptr res = add_setin(dd_manager, v1, v);
	    if(abs_expr_pre) {
	      bdd_ptr f = add_to_bdd(dd_manager, res);
	      bdd_ptr f1 = Abs_AbsAbstractFunc(f, abs_expr_pre);

              add_free(dd_manager, v1); add_free(dd_manager, v);
	      add_free(dd_manager, res);
	      bdd_free(dd_manager, f); bdd_free(dd_manager, f1);
	      v = bdd_to_add(dd_manager, f1);
	    } else {
	      add_free(dd_manager, v1); add_free(dd_manager, v);
	      v = res;
	    }
	  }
	}
	if (opt_verbose_level_gt(options, 2)){
	  indent_node(nusmv_stderr, "size of ", t1, " = ");
	  fprintf(nusmv_stderr, "%d ADD nodes\n", add_size(dd_manager, v)); /* To be performed */
	  dec_indent_size();
	}
	return(v);
      }
    }
  case TWODOTS:
    {
      int dim1, dim2, i;
      node_ptr t = Nil;

      dim1 = eval_num(car(expr), context);
      dim2 = eval_num(cdr(expr), context);
      for ( i=dim2; i>=dim1; i--)
        t = find_node(UNION, find_node(NUMBER, (node_ptr)i, Nil), t);
      if (t == Nil) rpterr("empty range: %d..%d", dim1, dim2);
      expr = t;
    } /* fall through on purpose here */
  case UNION: return(binary_op(dd_manager, add_union, expr, 1, 1, 1, context));
  case SETIN: return(binary_op(dd_manager, add_setin, expr, 1, 1, 1, context));
  default:
    internal_error("eval_recur: type = %d\n", node_get_type(expr));
    return((add_ptr)NULL);
  }
}

static node_ptr eval_big_case(node_ptr procs, node_ptr context)
{
  node_ptr list1, list2, tmp, col;
  add_ptr  cond, cond_;

  if(!procs || node_get_type(procs)==TRUEEXP) return Nil;
  if(node_get_type(procs)!=CASE)
    rpterr("somethiNG wronG");
  list2 = eval_big_case(cdr(procs), context);
  col = car(procs);
  if(node_get_type(col)!=COLON)
    rpterr("SOMETHING wRONg");

  list1 = eval_big_expr_recur(cdr(col), context);

  cond = eval(car(col), context);
  for(tmp=list1;tmp;tmp=cdr(cdr(tmp))) {
    add_ptr res = add_and(dd_manager, ((add_ptr)car(tmp)), cond);
    add_free(dd_manager, (add_ptr) car(tmp));
    tmp->left.bddtype = res;
  }

  cond_ = add_not(dd_manager, cond);
  for(tmp=list2;tmp;tmp=cdr(cdr(tmp))) {
    add_ptr res = add_and(dd_manager, ((add_ptr)car(tmp)), cond_);
    add_free(dd_manager, (add_ptr) car(tmp));
    tmp->left.bddtype = res;
  }
  add_free(dd_manager, cond); add_free(dd_manager, cond_);
  list1 = append(list1, list2);
  return list1;
}

static node_ptr eval_big_expr_recur(node_ptr expr, node_ptr context)
{
  if(!expr) return Nil;
  switch(node_get_type(expr)) {
    case DOT:
    case ATOM: {
      node_ptr name  = find_node(DOT, context, find_atom(expr));
      node_ptr temp1 = lookup_param_hash(name);
      node_ptr temp2 = lookup_symbol_hash(name);
      add_ptr  temp3 = (add_ptr)lookup_constant_hash(find_atom(expr));
      if ((temp1 && temp2) || (temp2 && temp3) || (temp3 && temp1))
	rpterr("atom \"%s\" is ambiguous", str_get_text(node_get_lstring(expr)));
      if(temp1) return eval_big_expr_recur(temp1, context);
      else if(temp2) {
	if(node_get_type(temp2) == VAR)
	  return cons((node_ptr)add_one(dd_manager), cons((node_ptr)
		      enforce_definition(eval_struct(expr, context)), Nil));
	return eval_big_expr_recur(temp2, context);
      } else if(temp3) {
	add_ptr one = add_one(dd_manager);
	add_ptr val = add_dup(temp3);
	return cons((node_ptr)one, cons((node_ptr)val, Nil));
      } else
	rpterr("sOMEthING wRoNG");
    }
    case ARRAY : rpterr("SoMeThInG wRoNg");
    case CASE: return eval_big_case(expr, context);
    case CONTEXT : return eval_big_expr_recur(cdr(expr), car(expr));
    case PLUS: {
      node_ptr list1 = eval_big_expr_recur(car(expr), context);
      node_ptr list2 = eval_big_expr_recur(cdr(expr), context);
      node_ptr res = binary_arithmetic_op(add_plus, list1, list2);
      add_free_list(list1); add_free_list(list2);
      return res;
    }
    case MINUS: {
      node_ptr list1 = eval_big_expr_recur(car(expr), context);
      node_ptr list2 = eval_big_expr_recur(cdr(expr), context);
      node_ptr res = binary_arithmetic_op(add_minus, list1, list2);
      add_free_list(list1); add_free_list(list2);
      return res;
    }
    case TIMES: {
      node_ptr list1 = eval_big_expr_recur(car(expr), context);
      node_ptr list2 = eval_big_expr_recur(cdr(expr), context);
      node_ptr res = binary_arithmetic_op(add_times, list1, list2);
      add_free_list(list1); add_free_list(list2);
    }
    case DIVIDE: {
      node_ptr list1 = eval_big_expr_recur(car(expr), context);
      node_ptr list2 = eval_big_expr_recur(cdr(expr), context);
      node_ptr res = binary_arithmetic_op(add_divide, list1, list2);
      add_free_list(list1); add_free_list(list2);
      return res;
    }
    case MOD: {
      node_ptr list1 = eval_big_expr_recur(car(expr), context);
      node_ptr list2 = eval_big_expr_recur(cdr(expr), context);
      node_ptr res = binary_arithmetic_op(add_mod, list1, list2);
      add_free_list(list1); add_free_list(list2);
      return res;
    }
    case NUMBER: {
      add_ptr one = add_one(dd_manager);
      add_ptr leaf = add_leaf(dd_manager, find_atom(expr));
      return cons((node_ptr) one, cons((node_ptr) leaf, Nil));
    }
    default:
      rpterr("SOmETHING wroNg");
  }
}

static node_ptr binary_arithmetic_op(ADDPFDAA op, node_ptr list1, node_ptr list2)
{
  node_ptr nlist;
  if(!list1) return Nil;
  nlist = binary_arithmetic_op(op, cdr(cdr(list1)), list2);
  return arithmetic_op_recur(op, list1, list2, nlist);
}

static node_ptr 
arithmetic_op_recur(ADDPFDAA op, node_ptr list1, node_ptr list2, node_ptr olist)
{
  node_ptr nlist;
  if(!list2) return olist;
  nlist = arithmetic_op_recur(op, list1, cdr(cdr(list2)), olist);
  {
    add_ptr cond1 = (add_ptr) car(list1);
    add_ptr cond2 = (add_ptr) car(list2);
    add_ptr intersect = add_and(dd_manager, cond1, cond2);
    add_ptr zero = add_zero(dd_manager);
    if(intersect!=zero) {
      add_ptr data1 = (add_ptr) car(cdr(list1));
      add_ptr data2 = (add_ptr) car(cdr(list2));
      add_ptr res = op(dd_manager, data1, data2);
      add_free(dd_manager, zero);
      return cons((node_ptr)intersect, cons((node_ptr)res, nlist));
    }
    add_free(dd_manager, zero);
    add_free(dd_manager, intersect);
    return nlist;
  }
}

static void add_free_list(node_ptr list)
{
  while(list) {
    node_ptr t = cdr(list);
    add_free(dd_manager, (add_ptr) car(list));
    free_node(list); list = t;
  }
}

