/* Copyright 2006 David Moews (dmoews@fastmail.fm)

This program 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 of the License, or
(at your option) any later version.

This program 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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <limits>
#include <cassert>
#include <cstdlib>
#include <iostream>

#include "BF.h"
#include "Integer.h"



// See if arg1 is within bounds arg2 & arg3; should have arg2 <= arg3.
bool is_within(const Integer &i, const Bound &lo, const Bound &hi)
{
  Integer rv = i;

  assert(lo.is_infinite() || hi.is_infinite() || lo.value() <= hi.value());
  return lo.is_le(i) && hi.is_ge(i);
}

// Trim arg1 to within bounds arg2 & arg3; should have arg2 <= arg3.
Integer trim_to(const Integer &i, const Bound &lo, const Bound &hi)
{
  Integer rv = i;

  assert(lo.is_infinite() || hi.is_infinite() || lo.value() <= hi.value());
  if (!lo.is_le(i))
     rv = lo.value();
  if (!hi.is_ge(i))
     rv = hi.value();
  return rv;
}

std::ostream &operator <<(std::ostream &o, const End &e)
{
  if (e.is_infinite())
  {
    o << "infinite";
    return o;
  }

  o << e.value() << ' ';
  switch (e.type())
  {
    case ET_wraparound:
         o << "wraparound";
         break;

    case ET_truncate:
         o << "limiting";
         break;

    case ET_abort:
         o << "abort";
         break;

    default:
         assert(0);
         break;
  }
  return o;
}

void BiVector::trim_bivector_at_left()
{
  if (left.is_le(start_at))
     return;
  if (left.value() >= start_at + (int)contents.size())
     contents.clear();
  else
     contents.erase(contents.begin(), 
                    contents.begin() + ((int)left.value() - start_at));
  start_at = (int)left.value();
}

void BiVector::trim_bivector_at_right() 
{
  if (right.is_ge(start_at + (int)contents.size() - 1))
     return;
  if (right.value() < start_at)
     contents.clear();
  else
     contents.erase(contents.end() +
                ((int)right.value() - (start_at + (int)contents.size() - 1)),
                    contents.end());
  start_at = (int)right.value() - ((int)contents.size() - 1);
}
  
// Return success/failure
bool BiVector::get(Integer i, Integer *p_v) const
{ 
  if (!left.is_le(i) || !right.is_ge(i))
     return false;
  if (i < start_at || i >= start_at + (int)contents.size())
  {
    if (p_v) *p_v = 0;
  } else
  {
    if (p_v) *p_v = contents[(int)i - start_at];
  }
  return true;
}

// Return success/failure
bool BiVector::set(Integer i, const Integer &v)
{ 
  if (!left.is_le(i) || !right.is_ge(i))
     return false;
  if (i < start_at)
  {
    contents.insert(contents.begin(), start_at - (int)i, (Integer)0);
    start_at = i;
  }
  else if (i >= start_at + (int)contents.size())
  {
    contents.insert(contents.end(), 
                    (int)i - start_at - ((int)contents.size() - 1), 
                    (Integer)0);
  }
  contents[(int)i - start_at] = v;
  return true;
}

// Reset left bound (must be to <= right bound.)
void BiVector::set_left(const Bound &e)
{
  assert(e.is_infinite() || right.is_infinite() || 
         e.value() <= right.value());
  left = e;
  trim_bivector_at_left();
}

// Reset right bound (must be to >= left bound.)
void BiVector::set_right(const Bound &e)
{
  assert(left.is_infinite() || e.is_infinite() || 
         left.value() <= e.value());
  right = e;
  trim_bivector_at_right();
}

// Trim all values in vector to be between LOW and HIGH, inclusive.
// We should have LOW <= 0, HIGH >= 0.
void BiVector::trim_values(const Bound &low, const Bound &high)
{
  assert(low.is_le(0) && high.is_ge(0));

  for (std::vector<Integer>::iterator i = contents.begin(); 
       i != contents.end(); i++)
  {
    if (!low.is_infinite() && *i < low.value()) 
       *i = low.value();
    if (!high.is_infinite() && *i > high.value())
       *i = high.value();
  }
}

// Return succ/failure
bool BF::set_left_end(const End &e) 
{ 
  if (!e.is_infinite() && !tape.get_right().is_infinite() && 
      e.value() > tape.get_right().value())
     return false;
  if ((e.is_infinite() && type_right == ET_wraparound) || 
      (e.type() == ET_wraparound && tape.get_right().is_infinite()))
     return false;

  tape.set_left(e.bound());
  type_left = e.type();
  position = trim_to(position, tape.get_left(), tape.get_right());
  return true;
}

// Return succ/failure
bool BF::set_right_end(const End &e) 
{ 
  if (!e.is_infinite() && !tape.get_left().is_infinite() && 
      tape.get_left().value() > e.value())
     return false;
  if ((e.is_infinite() && type_left == ET_wraparound) || 
      (e.type() == ET_wraparound && tape.get_left().is_infinite()))
     return false;

  tape.set_right(e.bound());
  type_right = e.type();
  position = trim_to(position, tape.get_left(), tape.get_right());
  return true;
}

// Return succ/failure
bool BF::set_low_end(const End &e) 
{
  if (!e.is_le(0))
     return false;
  if ((e.is_infinite() && high.type() == ET_wraparound) ||
      (e.type() == ET_wraparound && high.is_infinite()))
     return false;
  low = e;

  tape.trim_values(low.bound(), high.bound());
  eof_value = trim_to(eof_value, low.bound(), high.bound());
  return true;
}

// Return succ/failure
bool BF::set_high_end(const End &e) 
{ 
  if (!e.is_ge(0))
     return false;
  if ((e.is_infinite() && low.type() == ET_wraparound) ||
      (e.type() == ET_wraparound && low.is_infinite()))
     return false;
  high = e;

  tape.trim_values(low.bound(), high.bound());
  eof_value = trim_to(eof_value, low.bound(), high.bound());
  return true;
}

// Returns succ/failure
bool BF::set_code(const std::string &s) 
{ 
  size_t bracket_count = 0;

  for (std::string::const_iterator i = s.begin(); i != s.end(); i++)
  {
    if (*i == '[')
       bracket_count++;
    else if (*i == ']')
    {    
      if (bracket_count-- == 0)
         return false;
    }
  }
  if (bracket_count != 0)
     return false;

  code = s;
  breakpoints.clear();
  num_breakpoints_at.clear();
  num_breakpoints_at.insert(num_breakpoints_at.begin(), code.size(), 0u);
  pc = 0;
  return true;
}

// Returns succ/failure 
bool BF::set_pc(const Integer &i) 
{
  int ii;

  if (i < 0 || i >= std::numeric_limits<int>::max() )
     return false;
  ii = (int)i;
  if (ii > (int)code.length())
     return false;
  pc = ii;
  return true;
}

// Returns succ/failure 
bool BF::set_position(const Integer &i) 
{
  if (!tape.get_left().is_le(i) || !tape.get_right().is_ge(i))
     return false;
  position = i;
  return true;
}

int BF::get_breakpoint(const Integer &a) const
{
  int ia;

  if (a < 0 || a > std::numeric_limits<int>::max())
     return -1;
  ia = (int)a;
  if (ia >= breakpoints.size())
     return -1;
  return breakpoints[ia];
}

int BF::add_breakpoint(const Integer &a)
{ 
  int ia;

  if (a < 0 || a > std::numeric_limits<int>::max())
     return -1;
  ia = (int)a;
  if (ia >= code.size())
     return -1;
  breakpoints.push_back(ia);
  num_breakpoints_at[ia]++;
  return breakpoints.size() - 1;
}

bool BF::delete_breakpoint(const Integer &b) 
{ 
  int ib, place;

  if (b < 0 || b >= std::numeric_limits<int>::max())
     return false;
  ib = (int)b;
  if (ib >= breakpoints.size())
     return false;
  place = breakpoints[ib];
  if (place == -2)
     return true;

  assert(place >= 0 && place < num_breakpoints_at.size() &&
         num_breakpoints_at[place] > 0u);
  num_breakpoints_at[place]--;
  breakpoints[ib] = -2;
  return true;
}

//
// We compile a BF program into a sequence of these instructions.
//

// IT_While: BF `['
// IT_EndWhile: BF `]'
// IT_Input: BF `,'
// IT_Output: BF `.'

// IT_Start: start of execution for BF program
// IT_Breakpoint: breakpoint here
// IT_Halt: halt execution

// IT_AddPos: position += addend
// IT_AddCell: tape[position] += addend
// IT_SetMul: M := (tape[position] @ par_type, par_1, par_2, par_3),
//            where @ is the `compute multiplier' operation
// IT_AddCellMul: tape[position + cell] += addend * M
// IT_SetCell: tape[position] := addend

enum InstructionType 
{ 
  IT_While, IT_EndWhile, 
  IT_AddPos, IT_AddCell, IT_SetCell, IT_AddCellMul, IT_SetMul,
  IT_Start, IT_Breakpoint, IT_Input, IT_Output, IT_Halt
};

class Instruction
{
  InstructionType _type;
  int _location;
  int _addend;
  bool _par_type;                         // For IT_SetMult
  Integer _par_1, _par_2, _par_3;         // For IT_SetMult
  int _cell;
#ifdef DEBUG
  size_t _jump_to;
#endif
  std::vector<Instruction>::const_iterator _jump_ptr;

public:

  Instruction(InstructionType tt, int loc, int add = 0) 
       : _type(tt), _location(loc), _addend(add) { }

  Instruction(InstructionType tt, int loc, bool par_type,
              const Integer &par_1, const Integer &par_2, const Integer &par_3)
       : _type(tt), _location(loc), 
         _par_type(par_type), _par_1(par_1), _par_2(par_2), _par_3(par_3) { }

  Instruction(InstructionType tt, int loc, int add, int cell)
       : _type(tt), _location(loc), _addend(add), _cell(cell) { }
  
  InstructionType type() const { return _type; }
  int location() const { return _location; }
#ifdef DEBUG
  size_t jump_to() const { return _jump_to; }
#endif
  std::vector<Instruction>::const_iterator jump_ptr() const 
            { return _jump_ptr; }
  int addend() const { return _addend; }
  bool par_type() const { return _par_type; }
  Integer par_1() const { return _par_1; }
  Integer par_2() const { return _par_2; }
  Integer par_3() const { return _par_3; }
  int cell()  const { return _cell; }

#ifdef DEBUG
  void set_jump_to(size_t j) { _jump_to = j; }
#endif
  void set_jump_ptr(const std::vector<Instruction>::const_iterator &p)
           { _jump_ptr = p; }
  void set_addend(int j) { _addend = j; }
};

std::ostream &operator <<(std::ostream &o, const Instruction &i)
{
  o << "(PC=" << i.location() << ") ";
  switch (i.type())
  {
    case IT_Start:
         o << "start";
         break;
         
    case IT_Breakpoint:
         o << "breakpoint";
         break;
         
    case IT_Halt:
         o << "halt";
         break;
         
    case IT_While:
         o << "while";
#ifdef DEBUG
         o << " (-> " << i.jump_to() << ")";
#endif
         break;
         
    case IT_EndWhile:
         o << "endwhile";
#ifdef DEBUG
         o << " (-> " << i.jump_to() << ")";
#endif
         break;
         
    case IT_AddPos:
         o << "p += " << i.addend();
         break;
         
    case IT_AddCell:
         o << "T[p] += " << i.addend();
         break;
         
    case IT_SetCell:
         o << "T[p] = " << i.addend();
         break;
         
    case IT_SetMul:
         o << "M = multiplier(T[p]," 
           << (i.par_type() ? "true" : "false") << ","
           << i.par_1() << ","
           << i.par_2() << ","
           << i.par_3() << ")";
         break;
         
    case IT_AddCellMul:
         o << "T[p + " << i.cell() << "] += " << i.addend() << "* M";
         break;
         
    case IT_Input:
         o << "read T[p]";
         break;
         
    case IT_Output:
         o << "write T[p]";
         break;

    default:
         assert(0);
         break;
  }
  return o;
}

// 
// Try to remap a value to the specified range.  Return FALSE if we
// must abort, TRUE o.w.
// 
static bool remap(Integer *p_i, const End &lo, const End &hi)
{
  if (!lo.is_le(*p_i))
  {
    switch (lo.type())
    {
      case ET_abort:
      case ET_truncate:
           assert(!lo.is_infinite());
           *p_i = lo.value();
           if (lo.type() == ET_abort)
               return false;
           break;

      case ET_wraparound:
           assert(!lo.is_infinite() && !hi.is_infinite());
           *p_i = (*p_i - lo.value()) % (hi.value() - lo.value() + 1) 
                  + lo.value();
           break;
    }
  }
  else if (!hi.is_ge(*p_i))
  {
    switch (hi.type())
    {
      case ET_abort:
      case ET_truncate:
           assert(!hi.is_infinite());
           *p_i = hi.value();
           if (hi.type() == ET_abort)
               return false;
           break;

      case ET_wraparound:
           assert(!lo.is_infinite() && !hi.is_infinite());
           *p_i = (*p_i - lo.value()) % (hi.value() - lo.value() + 1) 
                  + lo.value();
           break;
    }
  }

  return true;
}

//
// Compute parameters for determining the number of times B must be 
// added to a number to reach 0 and put them into *P_T, *P_C, *P_D, and *P_E.
// LOW and HIGH are the bounds for the target number, which should be either 
// both finite & wraparound or both infinite.
//
void compute_multiplier_parameters(bool *p_t, 
                                   Integer *p_c, Integer *p_d, Integer *p_e,
                                   int b, const End &lo, const End &hi)
{
  if (lo.is_infinite())
  {
    assert(hi.is_infinite());
    if (p_t)
       *p_t = false;
    if (p_c)
       *p_c = b;
    if (p_d)
       *p_d = 0;
    if (p_e)
       *p_e = 0;
  } else
  {
    Integer s, t, g;

    assert(lo.type() == ET_wraparound && hi.type() == ET_wraparound);
    assert(!lo.is_infinite() && !hi.is_infinite());
    g = gcd_ext(hi.value() - lo.value() + 1, b, &s, &t);
//
// Ans. will be infinite if a % g != 0,
// or (-(a/g) * t) % ((hi.value() - lo.value() + 1) / g) o.w.
// Note that hi.value() - lo.value() + 1 > 0, so g is always positive.
//
    if (p_t)
       *p_t = true;
    if (p_c)
       *p_c = g;
    if (p_d)
       *p_d = (-t) % ((hi.value() - lo.value() + 1) / g);
    if (p_e)
       *p_e = (hi.value() - lo.value() + 1) / g;
  }
  /*NOTREACHED*/
} 

//
// Compute number of loop executions using parameters T, C, D, and E, and 
// integer A.  Returns FALSE if the number is infinite; otherwise, puts
// the number into *P_M and returns TRUE.
//
bool compute_multiplier(Integer *p_m, Integer a, 
                        bool t, const Integer &c, 
                        const Integer &d, const Integer &e)
{
  if (a == 0)
  {
    if (p_m)
       *p_m = 0;
    return true;
  }

  if (!t)
  {
    if (c == 0)
       return false;

    if (c < 0)
    {
      if (a < 0 || a % (-c) != 0)
         return false;
      if (p_m)
         *p_m = a / (-c);
    }
    else
    {
      if (a > 0 || (-a) % c != 0)
         return false;
      if (p_m)
         *p_m = (-a) / c;
    }
    return true;
  } 
  else
  {
    assert(c > 0);

    if (a % c != 0)
       return false;
    if (p_m)
       *p_m = ((a / c) * d) % e;
    return true;
  }
  /*NOTREACHED*/  
}

//
// Compile BF program into sequence of instructions.
// Returns success/failure.
//
bool compile(const std::string &code,
             int pc,
             const std::vector<unsigned> num_breakpoints_at,
             bool combine_cell_ops,
             bool combine_tape_ops,
             std::vector<Instruction> *p_insns)
{
  int incr;
  size_t n_brackets = 0;

  assert(p_insns != NULL);

  for (std::string::const_iterator i = code.begin(); i != code.end(); i++)
  {
    if (i - code.begin() == pc)
       p_insns->push_back(Instruction(IT_Start, i - code.begin()));

    if (num_breakpoints_at[i - code.begin()] != 0)
       p_insns->push_back(Instruction(IT_Breakpoint, i - code.begin()));

    switch (*i)
    {
      case '+':
      case '-':
         incr = (*i == '+' ? 1 : -1);
         if (combine_cell_ops
             && p_insns->size() != 0 
             && (p_insns->end() - 1)->type() == IT_AddCell)
         {
           (p_insns->end() - 1)->
                set_addend((p_insns->end() - 1)->addend() + incr);
           if ((p_insns->end() - 1)->addend() == 0)
              p_insns->pop_back();
         }
         else
            p_insns->push_back(Instruction(IT_AddCell, i - code.begin(), incr));
         break;

      case '<':
      case '>':
         incr = (*i == '>' ? 1 : -1);
         if (combine_tape_ops
             && p_insns->size() != 0 
             && (p_insns->end() - 1)->type() == IT_AddPos)
         {
           (p_insns->end() - 1)->
                set_addend((p_insns->end() - 1)->addend() + incr);
           if ((p_insns->end() - 1)->addend() == 0)
              p_insns->pop_back();
         }
         else
            p_insns->push_back(Instruction(IT_AddPos, i - code.begin(), incr));
         break;

      case ',': 
         p_insns->push_back(Instruction(IT_Input, i - code.begin()));
         break;

      case '.': 
         p_insns->push_back(Instruction(IT_Output, i - code.begin()));
         break;

      case '[': 
         n_brackets++;
         p_insns->push_back(Instruction(IT_While, i - code.begin()));
         break;

      case ']':
         if (n_brackets-- == 0)
            return false;

         p_insns->push_back(Instruction(IT_EndWhile, i - code.begin()));
         break;

      default:    // Comment, ignore
         break;
    }
  }

  if (code.size() == pc)
     p_insns->push_back(Instruction(IT_Start, code.size()));

  p_insns->push_back(Instruction(IT_Halt, code.size()));

  return n_brackets == 0;
}

//
// Assemble inner loops into AddCellMul instructions.
//
void optimize(std::vector<Instruction> *p_insns, const End &lo, const End &hi)
{
  std::vector<Instruction> insns_out;
  std::vector<Instruction>::const_iterator loop_start, j;
  size_t loop_start_out;
  int tape_movement;
  int counter_movement;

  for (std::vector<Instruction>::const_iterator i = p_insns->begin();
       i != p_insns->end();
       ++i)
  {
    insns_out.push_back(*i);

    switch (i->type())
    {
      case IT_While:
           loop_start = i;
           loop_start_out = insns_out.size() - 1;
           break;

      case IT_EndWhile:
           tape_movement = 0;
           counter_movement = 0;
           for (j = loop_start + 1; j != i; ++j)
           {
             if (j->type() != IT_AddCell && j->type() != IT_AddPos)
                break;
             if (j->type() == IT_AddPos)
                tape_movement += j->addend();
             else 
             {
               assert(j->type() == IT_AddCell);
               if (tape_movement == 0)
                   counter_movement += j->addend();
             }
           }
           if (i == j && tape_movement == 0)
           {
             bool par_type;
             Integer par1, par2, par3;   // Multiplier parameters

             insns_out.erase(insns_out.begin() + loop_start_out, 
                             insns_out.end());
             compute_multiplier_parameters(&par_type, &par1, &par2, &par3,
                                           counter_movement, lo, hi);
             //
             // We will only abort in the case of an infinite loop.
             // This is why we mark this instruction with PC equal to
             // that of the loop start.
             //
             insns_out.push_back(
                         Instruction(IT_SetMul, loop_start->location(),
                                     par_type, par1, par2, par3));
             insns_out.push_back(
                       Instruction(IT_SetCell, loop_start->location(), 0));
             tape_movement = 0;
             for (j = loop_start + 1; j != i; ++j)
             {
               if (j->type() == IT_AddPos)
                  tape_movement += j->addend();
               else 
               {
                 assert(j->type() == IT_AddCell);
                 if (tape_movement != 0)
                    insns_out.push_back(
                         Instruction(IT_AddCellMul, loop_start->location(),
                                     j->addend(), tape_movement));
               }
             }
             assert(tape_movement == 0);
           }
           break;

      default:
           break;
    }
  }

  *p_insns = insns_out;
}

struct Type_info
{
  bool is_unsigned;
  const char *name;
  const char *suffix;
  const char *format;
  const char *unsigned_coercer;
};

// Compile (as C program) to output stream.  Returns success/failure.
// Starts with PC=0, position=0, empty tape, no breakpoints.
// Parameters are as set in machine.  Compilation may be impossible for some
// choices of parameters.
bool BF::compile_to(std::ostream &o)
{
  std::vector<unsigned> no_breakpoints(code.size(), 0u);
  std::vector<Instruction> insns;
  Integer type_width, cell_delta, v, tape_length;
  unsigned long bits_in_type;

  enum { T_unsigned_char, T_int, T_unsigned, T_long, T_unsigned_long } 
       type_index;
  const Type_info *p_type_to_use;
  const Type_info info[] = 
  {{ true, "unsigned char", "u", "u", "" },
   { false, "int", "", "d", "(unsigned int)" },
   { true, "unsigned", "u", "u", "" },
   { false, "long", "l", "ld", "(unsigned long)" },
   { true, "unsigned long", "ul", "lu", "" },
  };

  if (type_left != ET_wraparound || type_right != ET_wraparound ||
      get_low_end().type() != ET_wraparound || 
      get_high_end().type() != ET_wraparound ||
      tape.get_left().is_infinite() || tape.get_right().is_infinite() ||
      get_low_end().is_infinite() || get_high_end().is_infinite() ||
      tape.get_left().value() > 0 || tape.get_right().value() < 0 ||
      get_low_end().value() > 0 || get_high_end().value() < 0)
      return false;
    
  if (get_low_end().value() == 0 && 
      get_high_end().value() == std::numeric_limits<unsigned char>::max())
     type_index = T_unsigned_char;
  else if (get_low_end().value() == 0 &&
           get_high_end().value() == std::numeric_limits<unsigned>::max())
     type_index = T_unsigned;
  else if (get_low_end().value() == 0 &&
           get_high_end().value() == std::numeric_limits<unsigned long>::max())
     type_index = T_unsigned_long;
  else if (get_low_end().value() == std::numeric_limits<int>::min() &&
           get_high_end().value() == std::numeric_limits<int>::max())
     type_index = T_int;
  else if (get_low_end().value() == std::numeric_limits<long>::min() &&
           get_high_end().value() == std::numeric_limits<long>::max())
     type_index = T_long;
  else
     return false;

  p_type_to_use = &info[type_index];
  type_width = get_high_end().value() - get_low_end().value() + 1;
  tape_length = tape.get_right().value() - tape.get_left().value() + 1;

  // Check to see if type limits consistent with binary representation.
  // Also, we refuse to compile to <8-bit types (<9-bit, if signed.)
  bits_in_type = type_width.power_of_2_dividing();
  if (bits_in_type < 8ul || type_width != pow(Integer(2), bits_in_type)
      || (bits_in_type == 8ul && !p_type_to_use->is_unsigned))
     return false;

  // Check to see if signed type consistent with binary representation.
  if (!p_type_to_use->is_unsigned)
  {
    if (get_low_end().value() != -pow(Integer(2), bits_in_type - 1) ||
        get_high_end().value() != pow(Integer(2), bits_in_type - 1) - 1)
       return false;
  }

  assert(can_combine_cell_ops() && can_combine_tape_ops());

  if (!compile(code, 0, no_breakpoints, true, true, &insns))
     return false;

  optimize(&insns, get_low_end(), get_high_end());

  // Emit code
  o << "#include <stdio.h>\n"
    << "#include <stdlib.h>\n"
    << '\n'
    << p_type_to_use->name << " a[" << tape_length << "ul];\n"
    << '\n'
    << "int main(int argc, char **argv)\n{\n"
    << "  size_t p = 0;\n"
    << "  " << p_type_to_use->name << " m;\n";
  if (get_input_method() == IM_decimal && type_index == T_unsigned_char)
     o << "  unsigned uc;\n";
  if (get_input_method() != IM_decimal)
     o << "  int c;\n";

  for (std::vector<Instruction>::const_iterator ip = insns.begin();
       ip != insns.end();
       ++ip)
  {
    switch (ip->type())
    {
      case IT_Start:
      case IT_Breakpoint:
               break;

      case IT_Halt:
               o << "  return 0;\n";
               break;

      case IT_SetCell:
               v = ip->addend();
               assert(remap(&v, get_low_end(), get_high_end()));
               o << "  a[p] = " << v << p_type_to_use->suffix << ";\n";
               break;

      case IT_SetMul:
               { 
                 assert(ip->par_type() && ip->par_1() > 0); 
                 unsigned long bits = ip->par_1().power_of_2_dividing();
                 Integer v = ip->par_2();
                 assert(remap(&v, get_low_end(), get_high_end()));
                 // par_1 s.b. a power of 2 (and so should par_3.)
                 assert(ip->par_1() == pow(Integer(2), bits));
                 if (bits == bits_in_type)
                 {
                   o << "  if (a[p])\n"
                     << "     return 2;\n"
                     << "  m = 0;\n";
                 } else
                 {
                   Integer a = ip->par_1() - 1;
                   assert(a >= 0 && a <= get_high_end().value());
                   if (a != 0)
                   {
                     o << "  if (a[p] & " 
                       << a << p_type_to_use->suffix << ")\n"
                       << "     return 2;\n";
                   }
                   o << "  m = ("
                     << (bits > 0 ? "(a[p] >> " : "a[p]");
                   if (bits > 0) 
                      o << bits << ")";
                   o << " * " << v << p_type_to_use->suffix
                     << ")";
                   if (ip->par_3() != 
                       get_high_end().value() - get_low_end().value() + 1)
                   {
                     v = ip->par_3() - 1;
                     assert(v >= 0 && v <= get_high_end().value());
                      o << " & " << v << p_type_to_use->suffix;
                   }
                   o << ";\n";
                 }
               }
               break;

      case IT_AddCellMul: 
               cell_delta = ((Integer)ip->cell()) % tape_length;
               v = ip->addend();
               assert(remap(&v, get_low_end(), get_high_end()));
               o << "  a[(p + " << cell_delta << "ul) % " << tape_length
                 << "ul ] += m * " << v << p_type_to_use->suffix
                 << ";\n";
               break;

      case IT_AddPos:
               cell_delta = ((Integer)ip->addend()) % tape_length;
               o << "  p = (p + " << cell_delta 
                                  << "ul) % " << tape_length << "ul;\n";
               break;

      case IT_AddCell:
               v = ip->addend();
               assert(remap(&v, get_low_end(), get_high_end()));
               o << "  a[p] += " << v << p_type_to_use->suffix << ";\n";
               break;

      case IT_While:
               o << "  while (a[p]) {\n";
               break;

      case IT_EndWhile:
               o << "  }\n";
               break;

      case IT_Output:
               if (get_output_method() == OM_decimal)
                  o << "  printf(\"%" << p_type_to_use->format
                                      << "\\n\", "
                    << ((type_index == T_unsigned_char) ? "(unsigned)" : "")
                                      << "a[p]);\n";
               else
                  o << "  putchar((unsigned char)" 
                    << p_type_to_use->unsigned_coercer << "a[p]);\n";
               break;

      case IT_Input:
               if (get_input_method() == IM_decimal)
                  o << "  if (1 != scanf(\"%" 
                    << p_type_to_use->format
                    << "\", &" 
                    << ((type_index == T_unsigned_char) ? "uc" : "a[p]")
                    << "))\n";
               else
                  o << "  if ((c = getchar()) == EOF)\n";
               switch (get_eof_method())
               {
                 case EOF_halt:
                      o << "     return 0;\n";
                      break;

                 case EOF_abort:
                      o << "     return 1;\n";
                      break;

                 case EOF_unchanged:
                      o << "     ;\n";
                      break;
 
                 case EOF_value:
                      o << "     a[p] = "
                        << get_eof_value() << p_type_to_use->suffix
                        << ";\n";
                      break;
               }
               switch (get_input_method())
               {
                 case IM_decimal:
                      if (type_index == T_unsigned_char)
                      {
                        o << "  else\n"
                          << "     a[p] = uc;\n";
                      }
                      break;

                 case IM_unsigned:
                      assert(get_high_end().value() >= 255);
                      o << "  else\n"
                        << "     a[p] = c;\n";
                      break;
 
                 case IM_signed:
                      if (!p_type_to_use->is_unsigned)
                      {
                        assert(get_low_end().value() <= -128);
                        assert(get_high_end().value() >= 127);
                        o << "     a[p] = ((c & 128) ? c - 256 : c);\n";
                      } else
                      {
                        assert(get_high_end().value() >= 127);
                        o << "     a[p] = ((c & 128) ? 0 : c);\n";
                      }
                      break;
               } 
               break;
    }
  }

  o << "}\n";

  return true;
}

// Run for given # of instructions (forever if # < 0)
RunTermination BF::run(const Integer &a)
{
  Integer mult, v, x, y;
  std::vector<Instruction> insns;
  bool first = true;

  if (!compile(code, pc, num_breakpoints_at, 
               a < 0 && can_combine_cell_ops(),
               a < 0 && can_combine_tape_ops(),
               &insns))
     return RT_Program_Invalid;

  if (a < 0 && can_combine_cell_ops() && can_combine_tape_ops())
     optimize(&insns, get_low_end(), get_high_end());

  // Fill in while...endwhile loop jumps and starting instruction pointer

  std::vector<Instruction>::const_iterator ip;
  std::vector<std::vector<Instruction>::iterator> while_locations;

  for (std::vector<Instruction>::iterator i = insns.begin();
       i != insns.end();
       ++i)
  {
    switch (i->type())
    {
      case IT_While:
           while_locations.push_back(i);
           break;

      case IT_EndWhile:
           if (while_locations.size() == 0)
              return RT_Program_Invalid;
           {
             std::vector<Instruction>::iterator start = 
                                             *(while_locations.end() - 1);

             i->set_jump_ptr(start);
             start->set_jump_ptr(i);
#ifdef DEBUG
             i->set_jump_to(start - insns.begin());
             start->set_jump_to(i - insns.begin());
#endif
             while_locations.pop_back();
           }
           break;

      case IT_Start:
           ip = i + 1;
           break;

      default:
           break;
    }
  }

#ifdef DEBUG
  std::cout << "Instructions:\n\n";
  for (std::vector<Instruction>::const_iterator i = insns.begin();
       i != insns.end();
       ++i)
      std::cout << i - insns.begin() << ": " << *i << '\n';
#endif

  for (int i = 0;; first = false, ++ip)
  {
    assert(is_within(position, tape.get_left(), tape.get_right()));
    if (i == a)
    {
      pc = ip->location();
      return RT_Chunk_Done;
    }
    switch (ip->type())
    {
      case IT_Start:
               break;

      case IT_Halt:
               pc = ip->location();
               return RT_Normal;
               break;

      case IT_Breakpoint:
               if (!first)
               {
                 pc = ip->location();
                 return RT_Breakpoint;
               }
               break;

      case IT_SetCell:
               v = ip->addend();
               assert(remap(&v, get_low_end(), get_high_end()));
               assert(tape.set(position, v));
               break;

      case IT_SetMul:
               assert(tape.get(position, &v));
               if (!compute_multiplier(&mult, v, 
                                       ip->par_type(), ip->par_1(), 
                                       ip->par_2(), ip->par_3()))
               {
                 pc = ip->location();
                 return RT_Infinite_Loop;
               }
               break;

      case IT_AddCellMul: 
               x = position + ip->cell();
               assert(remap(&x, get_left_end(), get_right_end()));
               assert(tape.get(x, &y));
               y += mult * ip->addend();
               assert(remap(&y, get_low_end(), get_high_end()));
               assert(tape.set(x, y));
               break;

      case IT_AddPos:
               position += ip->addend();
               if (!remap(&position, get_left_end(), get_right_end()))
               {
                 pc = ip->location();
                 return RT_Bound_Error;
               }
               ++i;
               break;

      case IT_AddCell:
               assert(tape.get(position, &v));
               v += ip->addend();
               if (!remap(&v, get_low_end(), get_high_end()))
               {
                 pc = ip->location();
                 return RT_Bound_Error;
               }
               assert(tape.set(position, v));
               ++i;
               break;

      case IT_While:
               assert(tape.get(position, &v));
               if (v == 0)
                  ip = ip->jump_ptr();
               ++i;
               break;

      case IT_EndWhile:
               assert(tape.get(position, &v));
               if (v != 0)
                  ip = ip->jump_ptr();
               ++i;
               break;

      case IT_Output:
               assert(tape.get(position, &v));
               switch (get_output_method())
               {
                 case OM_decimal:
                      std::cout << v << '\n';
                      break;

                 case OM_char:
                      // NB: (unsigned long)v.value_modulo_long() s.b. 
                      // congruent to v modulo 2^(8 sizeof(unsigned long)).
                      std::cout << 
                        (unsigned char)(unsigned long)(v.value_modulo_long());
                      break;
               }
               ++i;
               break;

      case IT_Input:
               {
                 bool at_eof;
                 int ichar;

                 switch (get_input_method())
                 {
                   case IM_decimal:
                        at_eof = !(std::cin >> v);
                        break;

                   case IM_signed:
                   case IM_unsigned:
                        at_eof = 
                          ((ichar = std::cin.get()) == 
                           std::char_traits<char>::eof());
                        if (get_input_method() == IM_signed && ichar >= 128)
                           v = ichar - 256;
                        else
                           v = ichar;
                        break;
                 }
                 if (at_eof)
                 {
                   switch (get_eof_method())
                   {
                     case EOF_halt:
                          pc = ip->location();
                          return RT_Normal;
                          break;

                     case EOF_abort:
                          pc = ip->location();
                          return RT_EOF_Error;
                          break;

                     case EOF_value:
                          assert(tape.set(position, get_eof_value()));
                          break;

                     case EOF_unchanged:
                          break;
                   }
                 } else
                 assert(tape.set(position, 
				 trim_to(v, low.bound(), high.bound())));
               }
               ++i;
               break;
    }
  }
  /*NOTREACHED*/
}
