/*  This file is part of GNU bc.

    Copyright (C) 1991-1994, 1997, 2006, 2008, 2012-2017 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 3 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, see
    <http://www.gnu.org/licenses>.

    You may contact the author by:
       e-mail:  philnelson@acm.org
      us-mail:  Philip A. Nelson
                Computer Science Department, 9062
                Western Washington University
                Bellingham, WA 98226-9062
       
*************************************************************************/
/* execute.c - run a bc program. */

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


/* The SIGINT interrupt handling routine. */

int had_sigint;

void
stop_execution ( int sig )
{
  had_sigint = TRUE;
}


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

unsigned char
byte ( program_counter *p )
{
  return (functions[p->pc_func].f_body[p->pc_addr++]);
}


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

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

  long const_base;

  bc_num temp_num;
  arg_list *auto_list;

  /* Initialize this run... */
  pc.pc_func = 0;
  pc.pc_addr = 0;
  runtime_error = FALSE;
  bc_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 && !had_sigint)
    {
      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);
		bc_out_num (temp->s_num, 10, out_char, std_only);
		depth++;
		temp = temp->s_next;
	      }
	    out_char ('\n');
	  }
      }
#endif

    switch ( inst )
      {

      case 'A' : /* increment array variable (Add one). */
	var_name = byte(&pc);
	if ((var_name & 0x80) != 0)
	  var_name = ((var_name & 0x7f) << 8) + 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 = !bc_is_zero (ex_stack->s_num);
	pop ();
	/*FALLTHROUGH*/ /* common branch and jump code */
      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;
          if (gp)
            pc.pc_addr = gp->l_adrs[l_off];
          else {
            rt_error ("Internal error.");
            break;
          }
	}
	break;

      case 'C' : /* Call a function. */
	/* Get the function number. */
	new_func = byte(&pc);
	if ((new_func & 0x80) != 0) 
	  new_func = ((new_func & 0x7f) << 8) + 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 & 0x7f) << 8) + 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 & 0x7f) << 8) + byte(&pc);
	decr_array (var_name);
	break;

      case 'O' : /* Write a string to the output with processing. */
	while ((ch = byte(&pc)) != '"')
	  if (ch != '\\')
	    out_schar (ch);
	  else
	    {
	      ch = byte(&pc);
	      if (ch == '"') break;
	      switch (ch)
		{
		case 'a':  out_schar (007); break;
		case 'b':  out_schar ('\b'); break;
		case 'f':  out_schar ('\f'); break;
		case 'n':  out_schar ('\n'); break;
		case 'q':  out_schar ('"'); break;
		case 'r':  out_schar ('\r'); break;
		case 't':  out_schar ('\t'); break;
		case '\\': out_schar ('\\'); break;
		default:  break;
		}
	    }
	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 & 0x7f ) << 8) + byte(&pc);
	store_array (var_name);
	break;

      case 'T' : /* Test tos for zero */
	c_code = bc_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. */
	bc_out_num (ex_stack->s_num, o_base, out_char, std_only);
	if (inst == 'W') out_char ('\n');
	store_var (4);  /* Special variable "last". */
	fflush (stdout);
	pop ();
	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 )
	    bc_int2num (&ex_stack->s_num, ex_stack->s_num->n_scale);
	  else
	    bc_int2num (&ex_stack->s_num, ex_stack->s_num->n_len
		     + ex_stack->s_num->n_scale);
	  break;
		
	case 'S':  /* Scale function. */ 
	  bc_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;

	case 'X': /* Random function. */
	  push_copy (_zero_);
	  bc_int2num (&ex_stack->s_num, random());
	  break;
	}
	break;

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

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

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

      case 'n' : /* Negate top of stack. */
	bc_sub (_zero_, ex_stack->s_num, &ex_stack->s_num, 0);
	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 & 0x7f) << 8) + byte(&pc);
	store_var (var_name);
	break;

      case 'w' : /* Write a string to the output. */
	while ((ch = byte(&pc)) != '"') out_schar (ch);
	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 1. */
	push_copy (_one_);
	break;

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

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

      case '|' : /* compare greater than */
	if (check_stack(2))
	  {
	    c_code = !bc_is_zero (ex_stack->s_next->s_num)
	      || !bc_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, 0);
	    pop();
	    pop();
	    push_num (temp_num);
	    bc_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, 0);
	    pop();
	    pop();
	    push_num (temp_num);
	    bc_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);
	    bc_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);
		bc_init_num (&temp_num);
	      }
	    else
	      rt_error ("Divide by zero");
	  }
	break;

      case '%' : /* remainder */
	if (check_stack(2))
	  {
	    if (bc_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);
		bc_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 (bc_is_zero (ex_stack->s_next->s_num) && bc_is_neg (ex_stack->s_num))
	      rt_error ("divide by zero");
	    pop();
	    pop();
	    push_num (temp_num);
	    bc_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 ("\ninterrupted execution.\n");
    }
}


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

int
prog_char (void)
{
  return (int) byte(&pc);
}


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

int
input_char (void)
{
  int 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();
	  out_col = 0;  /* Saw a new line */
	}
    }

  /* Classify and preprocess the input character. */
  if (isdigit(in_ch))
    return (in_ch - '0');
  if (in_ch >= 'A' && in_ch <= 'Z')
    return (in_ch + 10 - 'A');
  if (in_ch >= 'a' && in_ch <= 'z')
    return (in_ch + 10 - 'a');
  if (in_ch == '.' || in_ch == '+' || in_ch == '-')
    return (in_ch);
  if (in_ch == '~')
    return (':');
  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 (int (*in_char)(VOID), int conv_base)
{
  int digits;
  bc_num build, temp, result, mult, divisor;
  int   in_ch, first_ch;
  char  negative;

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

  /* The conversion base. */
  bc_int2num (&mult, conv_base);
  
  /* Get things ready. */
  in_ch = in_char();
  /* ~ is space returned by input_char(), prog_char does not return spaces. */
  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 < 36)
    {
      first_ch = in_ch;
      in_ch = in_char();
      if (in_ch < 36 && first_ch >= conv_base)
	first_ch = conv_base - 1;
      bc_int2num (&build, (int) first_ch);
    }

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

  push_num (build);
  bc_free_num (&temp);
  bc_free_num (&result);
  bc_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 (program_counter *progctr)
{
  bc_num build;
  program_counter look_pc;
  int kdigits, kscale;
  unsigned char inchar;
  char *ptr;
  
  /* Count the digits and get things ready. */
  look_pc = *progctr;
  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 progctr. */
  inchar = byte(progctr);
  
  /* Secial cases of 0, 1, and A-F single inputs. */
  if (kdigits == 1 && kscale == 0)
    {
      if (inchar == 0)
	{
	  push_copy (_zero_);
	  inchar = byte(progctr);
	  return;
	}
      if (inchar == 1) {
      push_copy (_one_);
      inchar = byte(progctr);
      return;
    }
    if (inchar > 9)
      {
	bc_init_num (&build);
	bc_int2num (&build, inchar);
	push_num (build);
	inchar = byte(progctr);
	return;
      }
    }

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

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


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

void
assign (char code)
{
  bc_free_num (&ex_stack->s_num);
  if (code)
    ex_stack->s_num = bc_copy_num (_one_);
  else
    ex_stack->s_num = bc_copy_num (_zero_);
}
