// file:    isc.cc
// author:  Robert M. Keller
// purpose: ISC (Incredibly-Simple Computer) simulator, loader, assembler
// $Id: isc.cc,v 1.9 1997/04/05 21:38:55 keller Exp keller $
// Modified 3/25/99 at the request of M. Fleck to permit number of registers
// to be specified on the command line.  Any integer number of registers can
// now be used.
//
// Command line syntax is now
//     isc [-][l][s][t][r NNNN] <input file>
// where NNNN is the number of registers.  There should be no space between
// the command-line letter options, e.g.
//     isc -tr 1000 rfac.isc
// means used program rfac.isc, with trace option and 1000 registers 

      int REGISTERS = 32;             // default number of machine registers
const int MEMORY  = 4096;             // memory words
const int INPUT_WORD =    -1;         // word from input device
const int INPUT_STATUS =  -2;         // status of input device
const int OUTPUT_WORD =   -3;         // word to output device
const int OUTPUT_STATUS = -4;         // status of output device
const int MAX_IMMEDIATE = 524288;     // maximum value of immediate
const int MIN_IMMEDIATE = -524287;    // minimum value of immediate
const int CBUFFSIZE = 100;            // source input buffer

#include <iostream.h>
#include <fstream.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include "safe_array.H"
#include "symbol_table.h"

typedef long address;                    // type of a memory address
typedef unsigned short reg_index;        // type of a register index
typedef int data;                        // type of data word

enum opcode                              // enumeration of opcodes
  {LOAD, STORE, COPY, ADD, SUB, MUL, DIV, SHR, SHL, AND, OR, COMP,
   JEQ, JNE, JGT, JGTE, JLT, JLTE, JSUB, JUNC, LIM, AIM};

opcode opcodes[] = {LOAD, STORE, COPY, ADD, SUB, MUL, DIV, SHR, SHL, AND, OR, COMP,
   JEQ, JNE, JGT, JGTE, JLT, JLTE, JSUB, JUNC, LIM, AIM};

enum psuedo_opcode                       // enumeration of pseudo-opcodes
  {ORIGIN, DEFINE, REGISTER, USE, RELEASE, TRACE, LABEL};

enum input_statuses { END_OF_FILE = -1, INPUT_BUSY = 0, INPUT_READY = 1 };

enum output_statuses { OUTPUT_BUSY = 0, OUTPUT_READY = 1 };

typedef int int24;                       // supposed to represent 24-bit signed integer

symbol_table ops(100);
symbol_table pops(100);
symbol_table id(100);                    // symbol table for user identifiers
symbol_table regid(100);		 // symbol table for register identifiers

struct instruction                       // The structure of an instruction:
    {
    opcode op;
    reg_index rix[3];                    // array of 3 register indices
    int24 imm_int;                       // immediate integer
    int line;                            // line number for trace purposes
    int trace;                           // whether to trace
    };                                   // (line is not in actual architecture)

enum tag_t {INST, DATA};

struct word                       // word is what can be stored in an address:
  {
  union
    {
    instruction inst;                    // an instruction,
    data Data;                           // or data
    }
  contents;
  tag_t tag;
  };

int TRAC;                                // global indicating trace wanted
int nogo;                                // 1 if error encounterd
int linecount;                           // line count for input errors

int input_status = INPUT_READY;          // status of input channel
int output_status = OUTPUT_READY;        // status of output channel
int input_word;                          // input buffer
int output_word;                         // output buffer


#define trace(Array, Flag, Index, Value) \
if( TRAC && Flag ) cout << "    " << #Array << "[" << (Index) << "] = " << (Value) << " ";

void error_termination(char *msg, char *str); // prototypes
void iotrace(char* string);

void reg_error(long i, long Elements)
{
cerr << "*** isc runtime: register index out of range: " << i << endl;
exit(1);
}

class processor
{
public:

processor(int n_registers, array<word> & Mem) // processor constructor
: mem(Mem), reg(n_registers, reg_error) 
{}

void run(address addr);			   // start processor at address

address show_contents(address ip);         // show contents (for listing, tracing)
void show_reg(word Word, int r);           // show register in instruction
void listing(address start, address stop); // generate code listing

private:

array<word> & mem;                       // memory
safe_array<data> reg;                    // declare the registers
address IP;                              // instruction pointer
instruction IR;                          // instruction register
address MAR;                             // memory address register
word MDR;                                // memory data register
data ALU_in[2];                          // ALU input registers
data ALU_out;                            // ALU output register

void fetch_and_execute();
void fetch_instruction();
void use_ALU(opcode op);
int  condition_ALU(opcode op);
void read_mem(int TRAC);
void write_mem(int TRAC);

int show_cont;				  // whether to show register contents
};


void processor::read_mem(int TRAC)
{ 
switch( MAR )				// dispatch on memory address contents
  {
  case INPUT_WORD:                            // get input word
       if( input_status != INPUT_READY )
         cerr << "*** warning: reading input when device not ready\n";
       MDR.contents.Data = input_word;
       if( TRAC ) cout << endl << "    reading from input word value " << MDR.contents.Data << endl;
       break;

  case INPUT_STATUS:                          // get status
       MDR.contents.Data = input_status;
       if( TRAC ) cout << endl << "    reading from input status value " << MDR.contents.Data << endl;
       break;

  case OUTPUT_WORD:                           // dummy
       MDR.contents.Data = 0;                          
         cerr << "*** warning: reading from output word, result always 0\n";
       break;

  case OUTPUT_STATUS:
       MDR.contents.Data = output_status;
       if( TRAC ) cout << endl << "    reading from output status value " << MDR.contents.Data << endl;
       break;

  default:
       if( MAR < 0 || MAR >= MEMORY )
         {
         cerr << "*** isc memory write to invalid location: " << MAR << endl;
         exit(1);
         }
       MDR = mem[MAR];
  }
}

void processor::write_mem(int Flag)
{ 
trace(mem, Flag, MAR, MDR.contents.Data);
switch( MAR )				// dispatch on memory address contents
  {
  case INPUT_STATUS:                    // setting input status to value being written
       if( Flag ) cout << endl << "    writing to input status value " << MDR.contents.Data << endl;;
       switch( MDR.contents.Data )
         {
         case INPUT_BUSY:               // setting to busy (start input)
              switch( input_status)
                {
                case INPUT_READY:
                     iotrace(" input ");
                     if( cin >> input_word )
                       {
                       if( Flag ) cout << input_word << endl;
                       input_status = INPUT_READY;
                       }
                     else
                       {
                       input_status = END_OF_FILE;
                       if( Flag ) cout << "end-of-file\n";
                       }
                     iotrace("-------");
                     break;
                default:
                     cerr << "*** warning: setting input_status while device busy\n";
                }
              break;
         default: cerr << "*** warning: should only be setting input_status to " 
                       << INPUT_BUSY << endl;
         }
       break;

  case INPUT_WORD:
         if( Flag ) cout << endl << "    writing to input word value " << MDR.contents.Data << endl;
         input_word = MDR.contents.Data;
         break;

  case OUTPUT_STATUS:
       if( Flag ) cout << endl << "    writing to output status value " << MDR.contents.Data << endl;
       switch( MDR.contents.Data )         // setting output status to value being written
         {
         case OUTPUT_BUSY:
           switch( output_status )
             {
             case OUTPUT_READY:
                  output_status = OUTPUT_BUSY;
                  iotrace(" output ");
                  cout << output_word << endl;
                  iotrace("-------");
                  output_status = OUTPUT_READY;
                  break;

             default:
                  cerr << "*** warning: setting output_status when busy" 
                       << OUTPUT_BUSY << endl;
             }
           break;
         default:  
           cerr << "*** warning: should only be setting output_status to " 
                << OUTPUT_BUSY << endl;
         }

  case OUTPUT_WORD:
       if( output_status != OUTPUT_READY )
         cerr << "*** warning: wrote to output_word when output not ready\n";
       output_word = MDR.contents.Data;
       break;

  default:
       if( MAR < 0 || MAR >= MEMORY )
	 {
	 cerr << "*** isc memory read from invalid location: " << MAR << endl;
	 exit(1);
	 }
       if( mem[MAR].tag == INST )
         {
         cerr << "*** isc runtime warning: writing into instruction at location: " << MAR << endl;
         }
       mem[MAR] = MDR;   
  }
}


// fetch_instruction fetches next instruction from memory

void processor::fetch_instruction()
{
  MAR = IP;			// load memory address with instruction location
  IP++;				// increment instruction pointer
  read_mem(0);			// read memory, to get instruction into MDR 
  IR = MDR.contents.inst;	// load instruction register with instruction
}


// use_ALU carries out ALU instruction, assuming data in ALU's input registers

void processor::use_ALU(opcode op)
{
switch( op )
  {
  case ADD:    ALU_out = ALU_in[0] + ALU_in[1]; break;
  case SUB:    ALU_out = ALU_in[0] - ALU_in[1]; break;
  case MUL:    ALU_out = ALU_in[0] * ALU_in[1]; break;
  case DIV:    ALU_out = ALU_in[0] / ALU_in[1]; break;
  case COPY:   ALU_out = ALU_in[0];             break;
  case SHR:    if( ALU_in[1] >= 0 )
                  ALU_out = ALU_in[0] >> ALU_in[1]; // shift right
               else
                  ALU_out = ALU_in[0] << ALU_in[1]; // shift left if negative
               break;
  case SHL:    if( ALU_in[1] >= 0 )
                  ALU_out = ALU_in[0] << ALU_in[1]; // shift left
               else
                  ALU_out = ALU_in[0] >> ALU_in[1]; // shift right if negative
               break;
  case AND:    ALU_out = ALU_in[0] & ALU_in[1]; break;
  case OR:     ALU_out = ALU_in[0] | ALU_in[1]; break;
  case COMP:   ALU_out = ~ALU_in[0]; break;
  default:
               cerr << "internal coding error, unrecognized ALU operation" << op << endl;
  }
}


// condition_ALU returns a condition bit for the various ALU register tests

int processor::condition_ALU(opcode op)
{
switch( op )
  {
  case JEQ:  return ALU_in[0] == ALU_in[1];
  case JNE:  return ALU_in[0] != ALU_in[1];
  case JGT:  return ALU_in[0] >  ALU_in[1];
  case JGTE: return ALU_in[0] >= ALU_in[1];
  case JLT:  return ALU_in[0] <  ALU_in[1];
  case JLTE: return ALU_in[0] <= ALU_in[1];
  }
return 0;
}


// fetch_and_execute carries out a complete processor cycle
// It calls fetch_instruction to get an instruction and partially decodes
// the instruction.  If the instruction involves the ALU, it
// loads the ALU operand registers, then transfers the result to a register
// or modifies the IP in the case of a jump.

void processor::fetch_and_execute()
{ 
fetch_instruction();		// get the instruction

switch( IR.op )			// dispatch on operation
  {
  case LOAD:  /* reg[R0] = mem[reg[R1]]; */
               MAR = reg[IR.rix[1]];
               read_mem(TRAC && IR.trace);           
               if( TRAC && IR.trace ) cout << "    reading from location " << MAR << " value " << MDR.contents.Data;
               trace(reg, IR.trace, IR.rix[0], MDR.contents.Data);
               reg[IR.rix[0]] = MDR.contents.Data;
               break;

  case STORE:  /* mem[reg[R0]] = reg[R1]; */
               MDR.tag = DATA;
               MDR.contents.Data = reg[IR.rix[1]]; 
               MAR = reg[IR.rix[0]];
               write_mem(TRAC && IR.trace);
               break;

  case ADD:    /* reg[R0] = reg[R1] + reg[R2];  */
  case SUB:    /* reg[R0] = reg[R1] - reg[R2];  */
  case MUL:    /* reg[R0] = reg[R1] * reg[R2];  */
  case DIV:    /* reg[R0] = reg[R1] / reg[R2];  */
  case SHR:    /* reg[R0] = reg[R1] >> reg[R2]; */
  case SHL:    /* reg[R0] = reg[R1] << reg[R2]; */
  case AND:    /* reg[R0] = reg[R1] && reg[R2]; */
  case OR:     /* reg[R0] = reg[R1] || reg[R2]; */

               ALU_in[1] = reg[IR.rix[2]];

  case COPY:   /* reg[R0] = reg[R1] */
  case COMP:   /* reg[R0] = ~reg[R1] */

               ALU_in[0] = reg[IR.rix[1]];
               use_ALU(IR.op);      
               trace(reg, IR.trace, IR.rix[0], ALU_out);
               reg[IR.rix[0]] = ALU_out;
               break;

  case JEQ:    /* jump to reg[R0] if reg[R1] == reg[R2] */
  case JNE:    /* jump to reg[R0] if reg[R1] != reg[R2] */    
  case JGT:    /* jump to reg[R0] if reg[R1] >  reg[R2] */    
  case JGTE:   /* jump to reg[R0] if reg[R1] >= reg[R2] */    
  case JLT:    /* jump to reg[R0] if reg[R1] <  reg[R2] */    
  case JLTE:   /* jump to reg[R0] if reg[R1] <= reg[R2] */    

               ALU_in[0] = reg[IR.rix[1]];
               ALU_in[1] = reg[IR.rix[2]];
               if( TRAC && IR.trace ) cout << "    jump to " << reg[IR.rix[0]];
               if( condition_ALU(IR.op) )	  /* test condition line */
                 IP = reg[IR.rix[0]];             /* jump to address in R0 */
               if( TRAC && IR.trace ) cout << (condition_ALU(IR.op) ? "" : " not") << " taken";
               break;

  case JSUB:   /* jump to subroutine at reg[R0], IP to reg[R1] */
               { 
               trace(reg, IR.trace, IR.rix[1], IP);
	       address temp = IP;                /* return address */
               IP = reg[IR.rix[0]];              /* jump to address in R0 */
               reg[IR.rix[1]] = temp;            /* save address in R1 */
               if( TRAC && IR.trace ) cout << "    jumping to sub at " << IP << " return is " << reg[IR.rix[1]];
               }
               break;

  case JUNC:   /* jump unconditionally to contents of reg[R0] */

               IP = reg[IR.rix[0]];              /* jump to address in R0 */
               if( TRAC && IR.trace ) cout << "    jumping unconditionally to " << IP;
               break;

  case LIM:    /* reg[R0] = immediate field in instruction */
               ALU_in[0] = IR.imm_int;
               use_ALU(COPY);
               reg[IR.rix[0]] = ALU_out;
               trace(reg, IR.trace, IR.rix[0], IR.imm_int);
               break;

  case AIM:    /* reg[R0] = reg[R0] + immediate field in instruction */
               ALU_in[0] = reg[IR.rix[0]];
               ALU_in[1] = IR.imm_int;
               use_ALU(ADD);
               reg[IR.rix[0]] = ALU_out;
               trace(reg, IR.trace, IR.rix[0], ALU_out);
               break;
  default:    
               error_termination("Unimplemented instruction", "");
  }
  if( TRAC && IR.trace ) cout << endl;
}

void processor::run(address addr)
{
IP = addr;
show_cont = 1;
if( TRAC ) cout << "            Begin trace\n";
do
  {
  if( TRAC && mem[IP].contents.inst.trace ) 
    {
    show_contents(IP);
    fetch_and_execute();
    cout << endl;
    }
  else
    fetch_and_execute();                        // interpret repeatedly
  }
while( IP > 0 );                                // jump to 0 implies halt
}

void showline()
{
cerr << "On line number " << linecount << " ";
}

void check_register_validity(reg_index R)      // check for validity of register
{
if( R >= REGISTERS )
  {
  showline();
  cerr << " ISC register index " << R << " out of range[0:" << REGISTERS-1 << "]\n";
  exit(1);
  }
}


void processor::show_reg(word Word, int r)   // show register in instruction
{
cout.form("r%-2d ", Word.contents.inst.rix[r]);
if(show_cont)
  cout.form("(%d) ", reg[Word.contents.inst.rix[r]]);
}


address processor::show_contents(address ip)   // show contents (for listing, tracing)
{
word Word;
Word = mem[ip];
cout.form("line:%5d loc:%5d contents: %-7s ", Word.contents.inst.line, ip++, ops.get_source(Word.contents.inst.op)); // show op
switch( Word.contents.inst.op )
  {
  case LIM:  /* register + value instruction */
  case AIM:
             show_reg(Word, 0);
             cout.form("%12d ", Word.contents.inst.imm_int);               // show addr
             break;

  case JUNC: /* one-register instruction */
             show_reg(Word, 0);
             break;

  case LOAD:  /* two-register instructions */
  case STORE:
  case COPY:
  case COMP:
  case JSUB:
             show_reg(Word, 0);
             show_reg(Word, 1);
             break;

  case ADD:   /* three-register instructions */
  case SUB:
  case MUL:
  case DIV:
  case SHR:
  case SHL:
  case AND:
  case OR:
  case JEQ:
  case JNE:    
  case JGT:   
  case JGTE:   
  case JLT:
  case JLTE:
             show_reg(Word, 0);
             show_reg(Word, 1);
             show_reg(Word, 2);
             break;
  default:
             break;
  }
cout << endl;  
return ip;
}

void processor::listing(address start, address stop)      // generate code listing
{
show_cont = 0;
cout << "            ISC code listing\n";
for( address VIP = start; VIP < stop; )
  {
  VIP = show_contents(VIP);
  }
cout << "            end of listing\n";
}

int skip_whitespace(istream &codefile)       // skip over any whitespace
{
char c;
while( codefile.get(c) )
  switch(c)
    {
    case '\n': linecount++; 
    case ' ':
    case '\t': break;          // continue with whitespace
    default: codefile.putback(c); return 1; // put back non-whitespace
    }
return 0;                             // end of file
}

void nogo_error(char* message1, char* message2)
{
  showline();
  cerr << message1 << " " <<  message2 << endl;
  nogo = 1;
}

int get_token(istream &codefile, char *buff)
{
char ch;
char *p;
int comment_char = 0;
p = buff;
if( !skip_whitespace(codefile) )
  return 0;                                     // end of file
while( codefile.get(ch) )
  {
  switch(ch)
    {
    case '\n': linecount++;
    case ' ':
    case '\t': *p = '\0'; 
      if( p != buff )
        return 1;                              // got token
      break;

    case '/':                                  // comment-handling hackery
      if( comment_char )                       // two successive /'s
        {
        *(--p) = '\0';                         // terminate  token
        while( codefile.get(ch) && ch != '\n') // move to end of line
          {}
        if( ch == '\n' ) linecount++;
        if( p != buff )
          return 1;                            // got token
        else 
          {
          if( !skip_whitespace(codefile) )
            return 0;                                     // end of file
          comment_char = 0;                    // reset
          }
        }
      else        
        {
        comment_char = 1;                      // first '/'
        *p++ = ch;
        }
      break;

    case '*':
      if( comment_char )                       // two successive /'s
        {
        *(--p) = '\0';                         // terminate  token
        comment_char = 0;
        while( codefile.get(ch) )
          {
          if( ch == '\n' ) linecount++;
          else if( ch == '*' )
            {
            char ch2;
            if( codefile.get(ch2) )
              {
              if( ch2 == '/' )		      // end of comment
                break;
              if( ch2 == '\n' ) linecount++;
              }
            }
          }
        if( p != buff )
          return 1;                            // got token
        }
      else        
        {
        *p++ = ch;
        }
      break;

    default: 
      comment_char = 0;
      *p++ = ch;
    }
  }
return 1;
}

void get_token(istream &codefile)
{
char buff[CBUFFSIZE];
get_token(codefile, buff);
}


// define possible status outcomes as an enumeration type
enum conversion_status {CLEAN_CONVERSION, OUT_OF_RANGE, CONVERSION_ERROR};

conversion_status string_to_long(char *string, long &number)
// sets number to long achieved from converting string
// The returned value of the function is:
// 0: if string can be converted cleanly
// 1: if string can be converted, but the result is out of range
// 2: if string can't be converted
{
char *endptr;
const int radix = 10;
errno = 0;                            // reset error indicator
number = strtol(string, &endptr, radix);     // try to convert to number
if( string[1] == '\0' && (string[0] == '+' || string[0] == '-') )
  return CONVERSION_ERROR;            // handle case that strtol doesn't catch
if( errno == ERANGE )                 // check for range error
  return OUT_OF_RANGE;
if( *endptr != '\0' )                 // check for residual string
  return CONVERSION_ERROR;
return CLEAN_CONVERSION;              // clean conversion
}

int get_argument(istream &codefile)
{
int index;
long value;
char buff[CBUFFSIZE];
get_token(codefile, buff);
switch( string_to_long(buff, value) )
  {
  case CLEAN_CONVERSION: 
       break;                              // value is direct integer in source
  case OUT_OF_RANGE: 
       showline(); error_termination("Invalid integer:", buff);
  case CONVERSION_ERROR: 
       index = id.find_source(buff);
       if( index < 0 )
            {
            nogo_error("Undefined symbol: ", buff);
            value = 0;                        // dummy value for now
            }
          else
            value = id.get_target(index);     // defined 
          break;
  }
return value;
}

int get_register(istream &codefile)
{
int index;
long value;
char buff[CBUFFSIZE];
get_token(codefile, buff);
switch( string_to_long(buff, value) )
  {
  case CLEAN_CONVERSION: 
       nogo_error("Using absolute number for register", buff);
       break;                              // value is direct integer in source
  case OUT_OF_RANGE: 
       showline(); error_termination("Invalid integer:", buff);
  case CONVERSION_ERROR: 
       index = regid.find_source(buff);
       if( index < 0 )
            {
            nogo_error("Undefined symbol: ", buff);
            value = 0;                        // dummy value for now
            }
          else
            value = regid.get_target(index);     // defined 
          break;
  }
return value;
}

void init_op_tables()
{
// put external form of opcodes into symbol table
ops.add("load",    LOAD);
ops.add("store",   STORE);
ops.add("copy",    COPY);
ops.add("add",     ADD);
ops.add("sub",     SUB);
ops.add("mul",     MUL);
ops.add("div",     DIV);
ops.add("shr",     SHR);
ops.add("shl",     SHL);
ops.add("and",     AND);
ops.add("or",      OR);
ops.add("comp",    COMP);
ops.add("jeq",     JEQ);
ops.add("jne",     JNE);
ops.add("jgt",     JGT);
ops.add("jgte",    JGTE);
ops.add("jlt",     JLT);
ops.add("jlte",    JLTE);
ops.add("jsub",    JSUB);
ops.add("junc",    JUNC);
ops.add("lim",     LIM);
ops.add("aim",     AIM);

pops.add("origin",   ORIGIN);
pops.add("define",   DEFINE);
pops.add("register", REGISTER);
pops.add("use",      USE);
pops.add("release",  RELEASE);
pops.add("trace",    TRACE);
pops.add("label",    LABEL);
}

int find_free_reg(array<int> &reg_used)
{
for( int i = 0; i < reg_used.length(); i++ )
  if( reg_used[i] == 0 )
    {
    reg_used[i] = 1;
    return i;
    }
nogo_error("not enough registers to allocate", "");
return -1;						// error indication
}

scan_pseudo_op(short pass, int code_index, istream &codefile, array<int> &reg_used, address &VIP, int &TRACELOC)
{
char cbuff[CBUFFSIZE];
char cbuff2[CBUFFSIZE];
long value, value2;                            // values of id
int index;
int new_reg;				       // index of register to be allocated by USE
int reg_def;

reg_def = 0;
switch( pops.get_target(code_index) )
  {
  case ORIGIN:                                 // origin designates to set VIP
       get_token(codefile, cbuff);
       switch(string_to_long(cbuff, value))
	 {
	 case CLEAN_CONVERSION: break;
	 case OUT_OF_RANGE: 
	 case CONVERSION_ERROR: 
		showline(); error_termination("Invalid integer:", cbuff);
	 }
       VIP = value;
       break;

  case TRACE:                                 // origin designates to set VIP
       get_token(codefile, cbuff);
       switch(string_to_long(cbuff, value))
	 {
	 case CLEAN_CONVERSION: break;
	 case OUT_OF_RANGE: 
	 case CONVERSION_ERROR: 
		showline(); error_termination("Invalid integer:", cbuff);
	 }
       TRACELOC = value;
       break;

  case REGISTER:			       // defining register identifier
       reg_def = 1;
  case DEFINE:
       get_token(codefile, cbuff);         // identifier being defined
       switch(string_to_long(cbuff, value))
	 {
	 case CLEAN_CONVERSION: 
	 case OUT_OF_RANGE: 
	      showline(); error_termination("Cannot redefine integer:", cbuff);
	 case CONVERSION_ERROR: break;            // symbol, as desired
	 }
       get_token(codefile, cbuff2);               // symbol's value
       switch(string_to_long(cbuff2, value2))
	 {
	 case CLEAN_CONVERSION: break;
	 case OUT_OF_RANGE: 
	 case CONVERSION_ERROR: 
	      showline(); error_termination("Invalid integer:", cbuff);
	 }
       index = id.find_source(cbuff);           
       if( index >= 0 )             // identifer already seen?
	 {
	 if( id.defined(index) > 0 )
	   {
	   if( reg_def )
	     {
	     if( reg_used[value2] )
	       {
	       cerr << "register " << value2 
		    << " to which " << cbuff << " is being defined is already in use " << endl;
	       nogo = 1;
	       }
             else
               reg_used[value2] = 1;
	     }
	   else
             if( pass == 1 )
	       {
	       nogo_error("Multiply defined:", cbuff);
	       }
	   }
         (reg_def ? regid : id).undefine(index);
	 (reg_def ? regid : id).set_target(index, value2);  // set the value
	 }
       else
	 {
	 (reg_def ? regid : id).add(cbuff, value2);
	 if( reg_def )
	   reg_used[value2] = 1;
	 }
       break;

  case USE:
       get_token(codefile, cbuff);
       index = regid.find_source(cbuff);
       if( index < 0 )                      // not used before
	 {
	 new_reg = find_free_reg(reg_used);
         if( new_reg >= 0 )
  	   regid.add(cbuff, new_reg);       // allocate register
	 }
       else
	 {
	 if( regid.defined(index) > 0 
          && reg_used[regid.get_target(index)]   // in use now
           )
	   {
           nogo_error("Register already in use:", cbuff);
	   }
	 else
	   {
	   new_reg = find_free_reg(reg_used);
           if( new_reg >= 0 ) 
	     regid.set_target(index, new_reg);     // allocate register
	   }
	 }
       break;

  case RELEASE:
       get_token(codefile, cbuff);
       index = regid.find_source(cbuff);
       if( index < 0 )                      // not used before
	 {
	 nogo_error("Can't release register not yet used: ", cbuff);
	 }
       else
	 {
	 if( reg_used[regid.get_target(index)] )   // in use now
	   {
	   reg_used[regid.get_target(index)] = 0;  // take out
	   regid.undefine(index);
	   }
	 else
	   {
	   nogo_error("Can't release register not in use:", cbuff);
	   }
	 }
       break;

  case LABEL:
       get_token(codefile, cbuff);
       index = id.find_source(cbuff);           
       if( index >= 0 )            // identifer already seen?
	 {
	 if( id.defined(index) > 0 )
	   {
           if( pass == 1 )
             { 
	     nogo_error(" Multiply defined:", cbuff);
             }
           id.undefine(index);
	   }
	 id.set_target(index, VIP);
	 }
       else
	 {
	 id.add(cbuff, VIP);
	 }
       break;
  }
}

address scan(istream & codefile) // scan file to form symbol table
{
char cbuff[CBUFFSIZE];                         // buffer for op codes
char cbuff2[CBUFFSIZE];                        // buffer for op codes
int  ibuff;                                    // buffer for int
int code_index;                                // opcode index or -1
linecount = 0;
address VIP = 0;                               // Virtual Instruction Pointer
int TRACELOC = 1;			       // whether to trace location

array<int> reg_used(REGISTERS);		// array indicating registers in use
int i;

for( i = 0; i < REGISTERS; i++ )
  reg_used[i] = 0;			// registers not used

while( get_token(codefile, cbuff) )            // read until end-of-file
  {
  code_index = pops.find_source(cbuff);                   // check if pseudo-op
  if( code_index >= 0 )
    {
    scan_pseudo_op(1, code_index, codefile, reg_used, VIP, TRACELOC);
    }
  else
    {
    code_index = ops.find_source(cbuff);                  // check if opcode
    if( code_index < 0 )
      {
      showline();
      error_termination("Invalid ISC Opcode: ", cbuff);
      }
    switch( ops.get_target(code_index) )
      {
      case JUNC:  /* one-register instructions */
                  get_token(codefile);
                  break;

      case LOAD:  /* two-register instructions */
      case STORE:
      case COPY:
      case COMP:
      case JSUB:
                  get_token(codefile);
                  get_token(codefile);
                  break;

      case ADD:   /* three-register instructions */
      case SUB:
      case MUL:
      case DIV:
      case SHR:
      case SHL:
      case AND:
      case OR:
      case JEQ:
      case JNE:    
      case JGT:   
      case JGTE:   
      case JLT:
      case JLTE:
                  get_token(codefile);
                  get_token(codefile);
                  get_token(codefile);
                  break;

      case LIM:   /* one-register, one-datum instruction */
      case AIM:
                  get_token(codefile);
                  get_token(codefile); // note any symbolic arguments
                  break;
      default:
                  cerr << "internal coding error, unrecognized instruction case" << code_index << endl;
      }
    VIP++;
    }
  }
return VIP--;
}


address load(istream & codefile, array<word> & mem) // load code from named stream
{
char cbuff[CBUFFSIZE];                         // buffer for op codes
int  value;                                    // buffer for int
int  code_index;                               // opcode or -1
word Word;                                     // word to be constructed
address VIP = 0;                               // Virtual Instruction Pointer
linecount = 0;
int TRACELOC = 1;

Word.tag = INST;	// for now, only load instructions

array<int> reg_used(REGISTERS);		// array indicating registers in use
int i;

for( i = 0; i < REGISTERS; i++ )
  reg_used[i] = 0;			// registers not used

while( get_token(codefile, cbuff) )            // read until end-of-file
  {
  code_index = pops.find_source(cbuff);
  if( code_index >= 0 )
    {
    scan_pseudo_op(2, code_index, codefile, reg_used, VIP, TRACELOC);
    }
  else
    {
    code_index = ops.find_source(cbuff);                     // check if opcode
    if( code_index < 0 )
      {
      showline();
      error_termination("invalid ISC Opcode: ", cbuff);
      }
    Word.contents.inst.trace = TRACELOC;
    Word.contents.inst.line = linecount+1;                            // keep line for trace
    Word.contents.inst.op = opcodes[ops.get_target(code_index)];      // put op in inst word
    switch( ops.get_target(code_index) )
      {
      case JUNC:  /* two-register instructions */
                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[0] = value;

                  Word.contents.inst.rix[1] = 0;
                  Word.contents.inst.rix[2] = 0;
                  break;

      case LOAD:  /* two-register instructions */
      case STORE:
      case COPY:
      case COMP:
      case JSUB:
                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[0] = value;

                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[1] = value;

                  Word.contents.inst.rix[2] = 0;
                  break;

      case ADD:   /* three-register instructions */
      case SUB:
      case MUL:
      case DIV:
      case SHR:
      case SHL:
      case AND:
      case OR:
      case JEQ:
      case JNE:    
      case JGT:   
      case JGTE:   
      case JLT:
      case JLTE:
                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[0] = value;

                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[1] = value;

                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[2] = value;
                  break;

      case LIM:                           // case using R0 and addr
      case AIM:
                  value = get_register(codefile);
                  check_register_validity(value);
                  Word.contents.inst.rix[0] = value;
                  Word.contents.inst.rix[1] = 0;
                  Word.contents.inst.rix[2] = 0;

                  value = get_argument(codefile);
                  if( value > MAX_IMMEDIATE || value < MIN_IMMEDIATE )
                    error_termination( "immediate operand exceeds 24 bits: ", cbuff);
                  Word.contents.inst.imm_int = value;  // get integer constant
                  break;
      default:
                  cerr << "internal coding error, unrecognized instruction case " << code_index << endl;
                  break;
      }
    mem[VIP++] = Word;
    }
  }
return VIP--;
}

int occurs(char c, char *str)                  // check if c occurs in str  
{
for( ; *str ; str++ )
  if( c == *str )
    return 1;
return 0;
}

void error_termination(char *msg, char *str)
{
cerr << "Error termination, " << msg << " " << str << endl;
exit(1);
}

ostream & operator<<(ostream & out, word w)  // overload << to accomodate word type
{ out << w.contents.Data; return out; }               // for tracing purposes

void iotrace(char* string)
{
if( TRAC ) 
  cout << "-------" << string << "------" << endl;
}

main(int argc, char **argv)                    // args are possible flag string
{                                              // and mandatory filename
if( argc < 2 || argc > 4)
  {
  cerr << "Usage: isc [-] [lstr <number of registers>] <code file> \n";
  exit(1);
  }
if( argc == 4 && occurs('r', argv[1]) )	       // number of registers specified
  {
  REGISTERS = atoi(argv[argc-2]);
  }
array<word> mem(MEMORY);                       // declare the memory
processor p(REGISTERS, mem);                   // declare a processor
address top;
init_op_tables();
char *&filename = argv[argc-1];
ifstream codefile(filename);                   // input file
if( !codefile )
  {
  cerr << "Code file " << filename << " not found\n";
  exit(1);
  }

for( int i = 0; i < MEMORY; i++ )	// initialize memory
  {
  mem[i].tag = DATA;
  mem[i].contents.Data = 0;
  }

nogo = 0;                               // assembler error flag
scan(codefile);
codefile.close();

if( argc > 2 && occurs('s', argv[1]) )         // symbol table option
  {
  cout << "             ISCAL Symbol Table\n";
  for( int i = 0; i < id.number(); i++ )
    {
    cout.form("%-30s", id.get_source(i));
    if( id.defined(i) < 1 )
      {
      cout << "currently undefined symbol\n";  // could be ok, e.g. released register
      }
    else cout.form("%16d\n", id.get_target(i));
    }

  cout << endl << "             Register Usage\n";
  for( int i = 0; i < regid.number(); i++ )
    {
    cout.form("%-30s", regid.get_source(i));
    if( regid.defined(i) < 1 )
      {
      cout << "currently undefined symbol\n";  // could be ok, e.g. released register
      }
    else cout.form("%16d\n", regid.get_target(i));
    }
  }

codefile.open(filename);
codefile.clear();
top = load(codefile, mem);
if( argc > 2 && occurs('l', argv[1]) )         // listing option?
  p.listing(0, top);

if( nogo )
  {
  cerr << "errors in source -- nogo\n";
  exit(1);
  }

TRAC = ( argc > 2 && occurs('t', argv[1]) );  // trace option?

p.run(0);
}
