/* 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 . 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 #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_); }