/* execute.c - run a bc program. */

/*  This file is part of bc written for MINIX.
    Copyright (C) 1991, 1992 Free Software Foundation, Inc.

    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; see the file COPYING.  If not, write to
    the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

    You may contact the author by:
       e-mail:  phil@cs.wwu.edu
      us-mail:  Philip A. Nelson
                Computer Science Department, 9062
                Western Washington University
                Bellingham, WA 98226-9062
       
*************************************************************************/

#include "bcdefs.h"
#include <signal.h>
#include "global.h"
#include "proto.h"


/* The SIGINT interrupt handling routine. */

int had_sigint;

void
stop_execution (sig)
     int sig;
{
  had_sigint = TRUE;
  printf ("\n");
  rt_error ("interrupted execution");
}


/* Get the current byte and advance the PC counter. */

unsigned char
byte (pc)
     program_counter *pc;
{
  int seg, offset;
    
  seg = pc->pc_addr >> BC_SEG_LOG;
  offset = pc->pc_addr++ % BC_SEG_SIZE;
  return (functions[pc->pc_func].f_body[seg][offset]);
}


/* The routine that actually runs the machine. */

void
execute ()
{
  int label_num, l_gp, l_off;
  bc_label_group *gp;
  
  char inst, ch;
  int  new_func;
  int  var_name;

  int const_base;

  bc_num temp_num;
  arg_list *auto_list;

  /* Initialize this run... */
  pc.pc_func = 0;
  pc.pc_addr = 0;
  runtime_error = FALSE;
  init_num (&temp_num);

  /* Set up the interrupt mechanism for an interactive session. */
  if (interactive)
    {
      signal (SIGINT, stop_execution);
      had_sigint = FALSE;
    }
   
  while (pc.pc_addr < functions[pc.pc_func].f_code_size && !runtime_error)
    {
      inst = byte(&pc);

#if DEBUG > 3
      { /* Print out address and the stack before each instruction.*/
	int depth; estack_rec *temp = ex_stack;
	
	printf ("func=%d addr=%d inst=%c\n",pc.pc_func, pc.pc_addr, inst);
	if (temp == NULL) printf ("empty stack.\n", inst);
	else
	  {
	    depth = 1;
	    while (temp != NULL)
	      {
		printf ("   %d = ", depth);
		out_num (temp->s_num, 10, out_char);
		depth++;
		temp = temp->s_next;
	      }
	  }
      }
#endif

    switch ( inst )
      {

      case 'A' : /* increment array variable (Add one). */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	incr_array (var_name);
	break;

      case 'B' : /* Branch to a label if TOS != 0. Remove value on TOS. */
      case 'Z' : /* Branch to a label if TOS == 0. Remove value on TOS. */
	c_code = !is_zero (ex_stack->s_num);
	pop ();
      case 'J' : /* Jump to a label. */
	label_num = byte(&pc);  /* Low order bits first. */
	label_num += byte(&pc) << 8;
	if (inst == 'J' || (inst == 'B' && c_code)
	    || (inst == 'Z' && !c_code)) {
	  gp = functions[pc.pc_func].f_label;
	  l_gp  = label_num >> BC_LABEL_LOG;
	  l_off = label_num % BC_LABEL_GROUP;
	  while (l_gp-- > 0) gp = gp->l_next;
	  pc.pc_addr = gp->l_adrs[l_off];
	}
	break;

      case 'C' : /* Call a function. */
	/* Get the function number. */
	new_func = byte(&pc);
	if ((new_func & 0x80) != 0) 
	  new_func = ((new_func << 8) & 0x7f) + byte(&pc);

	/* Check to make sure it is defined. */
	if (!functions[new_func].f_defined)
	  {
	    rt_error ("Function %s not defined.", f_names[new_func]);
	    break;
	  }

	/* Check and push parameters. */
	process_params (&pc, new_func);

	/* Push auto variables. */
	for (auto_list = functions[new_func].f_autos;
	     auto_list != NULL;
	     auto_list = auto_list->next)
	  auto_var (auto_list->av_name);

	/* Push pc and ibase. */
	fpush (pc.pc_func);
	fpush (pc.pc_addr);
	fpush (i_base);

	/* Reset pc to start of function. */
	pc.pc_func = new_func;
	pc.pc_addr = 0;
	break;

      case 'D' : /* Duplicate top of stack */
	push_copy (ex_stack->s_num);
	break;

      case 'K' : /* Push a constant */
	/* Get the input base and convert it to a bc number. */
	if (pc.pc_func == 0) 
	  const_base = i_base;
	else
	  const_base = fn_stack->s_val;
	if (const_base == 10)
	  push_b10_const (&pc);
	else
	  push_constant (prog_char, const_base);
	break;

      case 'L' : /* load array variable */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	load_array (var_name);
	break;

      case 'M' : /* decrement array variable (Minus!) */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	decr_array (var_name);
	break;

      case 'O' : /* Write a string to the output with processing. */
	while ((ch = byte(&pc)) != '"')
	  if (ch != '\\')
	    out_char (ch);
	  else
	    {
	      ch = byte(&pc);
	      if (ch == '"') break;
	      switch (ch)
		{
		case 'n':  out_char ('\n'); break;
		case 't':  out_char ('\t'); break;
		case 'r':  out_char ('\r'); break;
		case 'b':  out_char (007); break;
		case 'f':  out_char ('\f'); break;
		case '\\': out_char ('\\'); break;
		default:  break;
		}
	    }
	if (interactive) fflush (stdout);
	break;

      case 'R' : /* Return from function */
	if (pc.pc_func != 0)
	  {
	    /* "Pop" autos and parameters. */
	    pop_vars(functions[pc.pc_func].f_autos);
	    pop_vars(functions[pc.pc_func].f_params);
	    /* reset the pc. */
	    fpop ();
	    pc.pc_addr = fpop ();
	    pc.pc_func = fpop ();
	  }
	else
	  rt_error ("Return from main program.");
	break;

      case 'S' : /* store array variable */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	store_array (var_name);
	break;

      case 'T' : /* Test tos for zero */
	c_code = is_zero (ex_stack->s_num);
	assign (c_code);
	break;

      case 'W' : /* Write the value on the top of the stack. */
      case 'P' : /* Write the value on the top of the stack.  No newline. */
	out_num (ex_stack->s_num, o_base, out_char);
	if (inst == 'W') out_char ('\n');
	store_var (3);  /* Special variable "last". */
	if (interactive) fflush (stdout);
	break;

      case 'c' : /* Call special function. */
	new_func = byte(&pc);

      switch (new_func)
	{
	case 'L':  /* Length function. */
	  /* For the number 0.xxxx,  0 is not significant. */
	  if (ex_stack->s_num->n_len == 1 &&
	      ex_stack->s_num->n_scale != 0 &&
	      ex_stack->s_num->n_value[0] == 0 )
	    int2num (&ex_stack->s_num, ex_stack->s_num->n_scale);
	  else
	    int2num (&ex_stack->s_num, ex_stack->s_num->n_len
		     + ex_stack->s_num->n_scale);
	  break;
		
	case 'S':  /* Scale function. */ 
	  int2num (&ex_stack->s_num, ex_stack->s_num->n_scale);
	  break;

	case 'R':  /* Square Root function. */
	  if (!bc_sqrt (&ex_stack->s_num, scale))
	    rt_error ("Square root of a negative number");
	  break;

	case 'I': /* Read function. */
	  push_constant (input_char, i_base);
	  break;
	}
	break;

      case 'd' : /* Decrement number */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	decr_var (var_name);
	break;
      
      case 'h' : /* Halt the machine. */
	exit (0);

      case 'i' : /* increment number */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	incr_var (var_name);
	break;

      case 'l' : /* load variable */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	load_var (var_name);
	break;

      case 'n' : /* Negate top of stack. */
	bc_sub (_zero_, ex_stack->s_num, &ex_stack->s_num);
	break;

      case 'p' : /* Pop the execution stack. */
	pop ();
	break;

      case 's' : /* store variable */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name << 8) & 0x7f) + byte(&pc);
	store_var (var_name);
	break;

      case 'w' : /* Write a string to the output. */
	while ((ch = byte(&pc)) != '"') out_char (ch);
	if (interactive) fflush (stdout);
	break;
		   
      case 'x' : /* Exchange Top of Stack with the one under the tos. */
	if (check_stack(2)) {
	  bc_num temp = ex_stack->s_num;
	  ex_stack->s_num = ex_stack->s_next->s_num;
	  ex_stack->s_next->s_num = temp;
	}
	break;

      case '0' : /* Load Constant 0. */
	push_copy (_zero_);
	break;

      case '1' : /* Load Constant 0. */
	push_copy (_one_);
	break;

      case '!' : /* Negate the boolean value on top of the stack. */
	c_code = is_zero (ex_stack->s_num);
	assign (c_code);
	break;

      case '&' : /* compare greater than */
	if (check_stack(2))
	  {
	    c_code = !is_zero (ex_stack->s_next->s_num)
	      && !is_zero (ex_stack->s_num);
	    pop ();
	    assign (c_code);
	  }
	break;

      case '|' : /* compare greater than */
	if (check_stack(2))
	  {
	    c_code = !is_zero (ex_stack->s_next->s_num)
	      || !is_zero (ex_stack->s_num);
	    pop ();
	    assign (c_code);
	  }
	break;

      case '+' : /* add */
	if (check_stack(2))
	  {
	    bc_add (ex_stack->s_next->s_num, ex_stack->s_num, &temp_num);
	    pop();
	    pop();
	    push_num (temp_num);
	    init_num (&temp_num);
	  }
	break;

      case '-' : /* subtract */
	if (check_stack(2))
	  {
	    bc_sub (ex_stack->s_next->s_num, ex_stack->s_num, &temp_num);
	    pop();
	    pop();
	    push_num (temp_num);
	    init_num (&temp_num);
	  }
	break;

      case '*' : /* multiply */
	if (check_stack(2))
	  {
	    bc_multiply (ex_stack->s_next->s_num, ex_stack->s_num,
			 &temp_num, scale);
	    pop();
	    pop();
	    push_num (temp_num);
	    init_num (&temp_num);
	  }
	break;

      case '/' : /* divide */
	if (check_stack(2))
	  {
	    if (bc_divide (ex_stack->s_next->s_num,
			   ex_stack->s_num, &temp_num, scale) == 0)
	      {
		pop();
		pop();
		push_num (temp_num);
		init_num (&temp_num);
	      }
	    else
	      rt_error ("Divide by zero");
	  }
	break;

      case '%' : /* remainder */
	if (check_stack(2))
	  {
	    if (is_zero (ex_stack->s_num))
	      rt_error ("Modulo by zero");
	    else
	      {
		bc_modulo (ex_stack->s_next->s_num,
			   ex_stack->s_num, &temp_num, scale);
		pop();
		pop();
		push_num (temp_num);
		init_num (&temp_num);
	      }
	  }
	break;

      case '^' : /* raise */
	if (check_stack(2))
	  {
	    bc_raise (ex_stack->s_next->s_num,
		      ex_stack->s_num, &temp_num, scale);
	    if (is_zero (ex_stack->s_next->s_num) && is_neg (ex_stack->s_num))
	      rt_error ("divide by zero");
	    pop();
	    pop();
	    push_num (temp_num);
	    init_num (&temp_num);
	  }
	break;

      case '=' : /* compare equal */
	if (check_stack(2))
	  {
	    c_code = bc_compare (ex_stack->s_next->s_num,
				 ex_stack->s_num) == 0;
	    pop ();
	    assign (c_code);
	  }
	break;

      case '#' : /* compare not equal */
	if (check_stack(2))
	  {
	    c_code = bc_compare (ex_stack->s_next->s_num,
				 ex_stack->s_num) != 0;
	    pop ();
	    assign (c_code);
	  }
	break;

      case '<' : /* compare less than */
	if (check_stack(2))
	  {
	    c_code = bc_compare (ex_stack->s_next->s_num,
				 ex_stack->s_num) == -1;
	    pop ();
	    assign (c_code);
	  }
	break;

      case '{' : /* compare less than or equal */
	if (check_stack(2))
	  {
	    c_code = bc_compare (ex_stack->s_next->s_num,
				 ex_stack->s_num) <= 0;
	    pop ();
	    assign (c_code);
	  }
	break;

      case '>' : /* compare greater than */
	if (check_stack(2))
	  {
	    c_code = bc_compare (ex_stack->s_next->s_num,
				 ex_stack->s_num) == 1;
	    pop ();
	    assign (c_code);
	  }
	break;

      case '}' : /* compare greater than or equal */
	if (check_stack(2))
	  {
	    c_code = bc_compare (ex_stack->s_next->s_num,
				 ex_stack->s_num) >= 0;
	    pop ();
	    assign (c_code);
	  }
	break;

	default  : /* error! */
	  rt_error ("bad instruction: inst=%c", inst);
      }
    }

  /* Clean up the function stack and pop all autos/parameters. */
  while (pc.pc_func != 0)
    {
      pop_vars(functions[pc.pc_func].f_autos);
      pop_vars(functions[pc.pc_func].f_params);
      fpop ();
      pc.pc_addr = fpop ();
      pc.pc_func = fpop ();
    }

  /* Clean up the execution stack. */ 
  while (ex_stack != NULL) pop();

  /* Clean up the interrupt stuff. */
  if (interactive)
    {
      signal (SIGINT, use_quit);
      if (had_sigint)
	printf ("Interruption completed.\n");
    }
}


/* Prog_char gets another byte from the program.  It is used for
   conversion of text constants in the code to numbers. */

char
prog_char ()
{
  return byte(&pc);
}


/* Read a character from the standard input.  This function is used
   by the "read" function. */

char
input_char ()
{
  char in_ch;
  
  /* Get a character from the standard input for the read function. */
  in_ch = getchar();

  /* Check for a \ quoted newline. */
  if (in_ch == '\\')
    {
      in_ch = getchar();
      if (in_ch == '\n')
	in_ch = getchar();
    }

  /* Classify and preprocess the input character. */
  if (isdigit(in_ch))
    return (in_ch - '0');
  if (in_ch >= 'A' && in_ch <= 'F')
    return (in_ch + 10 - 'A');
  if (in_ch >= 'a' && in_ch <= 'f')
    return (in_ch + 10 - 'a');
  if (in_ch == '.' || in_ch == '+' || in_ch == '-')
    return (in_ch);
  if (in_ch <= ' ')
    return (' ');
  
  return (':');
}


/* Push_constant converts a sequence of input characters as returned
   by IN_CHAR into a number.  The number is pushed onto the execution
   stack.  The number is converted as a number in base CONV_BASE. */

void
push_constant (in_char, conv_base)
   char (*in_char)(VOID);
   int conv_base;
{
  int digits;
  bc_num build, temp, result, mult, divisor;
  char  in_ch, first_ch;
  char  negative;

  /* Initialize all bc numbers */
  init_num (&temp);
  init_num (&result);
  init_num (&mult);
  build = copy_num (_zero_);
  negative = FALSE;

  /* The conversion base. */
  int2num (&mult, conv_base);
  
  /* Get things ready. */
  in_ch = in_char();
  while (in_ch == ' ')
    in_ch = in_char();

  if (in_ch == '+')
    in_ch = in_char();
  else
    if (in_ch == '-')
      {
	negative = TRUE;
	in_ch = in_char();
      }

  /* Check for the special case of a single digit. */
  if (in_ch < 16)
    {
      first_ch = in_ch;
      in_ch = in_char();
      if (in_ch < 16 && first_ch >= conv_base)
	first_ch = conv_base - 1;
      int2num (&build, (int) first_ch);
    }

  /* Convert the integer part. */
  while (in_ch < 16)
    {
      if (in_ch < 16 && in_ch >= conv_base) in_ch = conv_base-1;
      bc_multiply (build, mult, &result, 0);
      int2num (&temp, (int) in_ch);
      bc_add (result, temp, &build);
      in_ch = in_char();
    }
  if (in_ch == '.')
    {
      in_ch = in_char();
      if (in_ch >= conv_base) in_ch = conv_base-1;
      free_num (&result);
      free_num (&temp);
      divisor = copy_num (_one_);
      result = copy_num (_zero_);
      digits = 0;
      while (in_ch < 16)
	{
	  bc_multiply (result, mult, &result, 0);
	  int2num (&temp, (int) in_ch);
	  bc_add (result, temp, &result);
	  bc_multiply (divisor, mult, &divisor, 0);
	  digits++;
	  in_ch = in_char();
	  if (in_ch < 16 && in_ch >= conv_base) in_ch = conv_base-1;
	}
      bc_divide (result, divisor, &result, digits);
      bc_add (build, result, &build);
    }
  
  /* Final work.  */
  if (negative)
    bc_sub (_zero_, build, &build);

  push_num (build);
  free_num (&temp);
  free_num (&result);
  free_num (&mult);
}


/* When converting base 10 constants from the program, we use this
   more efficient way to convert them to numbers.  PC tells where
   the constant starts and is expected to be advanced to after
   the constant. */

void
push_b10_const (pc)
     program_counter *pc;
{
  bc_num build;
  program_counter look_pc;
  int kdigits, kscale;
  char inchar;
  char *ptr;
  
  /* Count the digits and get things ready. */
  look_pc = *pc;
  kdigits = 0;
  kscale  = 0;
  inchar = byte (&look_pc);
  while (inchar != '.' && inchar != ':')
    {
      kdigits++;
      inchar = byte(&look_pc);
    }
  if (inchar == '.' )
    {
      inchar = byte(&look_pc);
      while (inchar != ':')
	{
	  kscale++;
	  inchar = byte(&look_pc);
	}
    }

  /* Get the first character again and move the pc. */
  inchar = byte(pc);
  
  /* Secial cases of 0, 1, and A-F single inputs. */
  if (kdigits == 1 && kscale == 0)
    {
      if (inchar == 0)
	{
	  push_copy (_zero_);
	  inchar = byte(pc);
	  return;
	}
      if (inchar == 1) {
      push_copy (_one_);
      inchar = byte(pc);
      return;
    }
    if (inchar > 9)
      {
	init_num (&build);
	int2num (&build, inchar);
	push_num (build);
	inchar = byte(pc);
	return;
      }
    }

  /* Build the new number. */
  if (kdigits == 0)
    {
      build = new_num (1,kscale);
      ptr = build->n_value;
      *ptr++ = 0;
    }
  else
    {
      build = new_num (kdigits,kscale);
      ptr = build->n_value;
    }

  while (inchar != ':')
    {
      if (inchar != '.')
	if (inchar > 9)
	  *ptr++ = 9;
	else
	  *ptr++ = inchar;
      inchar = byte(pc);
    }
  push_num (build);
}


/* Put the correct value on the stack for C_CODE.  Frees TOS num. */

void
assign (c_code)
     char c_code;
{
  free_num (&ex_stack->s_num);
  if (c_code)
    ex_stack->s_num = copy_num (_one_);
  else
    ex_stack->s_num = copy_num (_zero_);
}
