| # This file provides the runtime support for running a basic program |
| # Assumes the program has been parsed using basparse.py |
| |
| import sys |
| import math |
| import random |
| |
| |
| class BasicInterpreter: |
| |
| # Initialize the interpreter. prog is a dictionary |
| # containing (line,statement) mappings |
| def __init__(self, prog): |
| self.prog = prog |
| |
| self.functions = { # Built-in function table |
| 'SIN': lambda z: math.sin(self.eval(z)), |
| 'COS': lambda z: math.cos(self.eval(z)), |
| 'TAN': lambda z: math.tan(self.eval(z)), |
| 'ATN': lambda z: math.atan(self.eval(z)), |
| 'EXP': lambda z: math.exp(self.eval(z)), |
| 'ABS': lambda z: abs(self.eval(z)), |
| 'LOG': lambda z: math.log(self.eval(z)), |
| 'SQR': lambda z: math.sqrt(self.eval(z)), |
| 'INT': lambda z: int(self.eval(z)), |
| 'RND': lambda z: random.random() |
| } |
| |
| # Collect all data statements |
| def collect_data(self): |
| self.data = [] |
| for lineno in self.stat: |
| if self.prog[lineno][0] == 'DATA': |
| self.data = self.data + self.prog[lineno][1] |
| self.dc = 0 # Initialize the data counter |
| |
| # Check for end statements |
| def check_end(self): |
| has_end = 0 |
| for lineno in self.stat: |
| if self.prog[lineno][0] == 'END' and not has_end: |
| has_end = lineno |
| if not has_end: |
| print("NO END INSTRUCTION") |
| self.error = 1 |
| return |
| if has_end != lineno: |
| print("END IS NOT LAST") |
| self.error = 1 |
| |
| # Check loops |
| def check_loops(self): |
| for pc in range(len(self.stat)): |
| lineno = self.stat[pc] |
| if self.prog[lineno][0] == 'FOR': |
| forinst = self.prog[lineno] |
| loopvar = forinst[1] |
| for i in range(pc + 1, len(self.stat)): |
| if self.prog[self.stat[i]][0] == 'NEXT': |
| nextvar = self.prog[self.stat[i]][1] |
| if nextvar != loopvar: |
| continue |
| self.loopend[pc] = i |
| break |
| else: |
| print("FOR WITHOUT NEXT AT LINE %s" % self.stat[pc]) |
| self.error = 1 |
| |
| # Evaluate an expression |
| def eval(self, expr): |
| etype = expr[0] |
| if etype == 'NUM': |
| return expr[1] |
| elif etype == 'GROUP': |
| return self.eval(expr[1]) |
| elif etype == 'UNARY': |
| if expr[1] == '-': |
| return -self.eval(expr[2]) |
| elif etype == 'BINOP': |
| if expr[1] == '+': |
| return self.eval(expr[2]) + self.eval(expr[3]) |
| elif expr[1] == '-': |
| return self.eval(expr[2]) - self.eval(expr[3]) |
| elif expr[1] == '*': |
| return self.eval(expr[2]) * self.eval(expr[3]) |
| elif expr[1] == '/': |
| return float(self.eval(expr[2])) / self.eval(expr[3]) |
| elif expr[1] == '^': |
| return abs(self.eval(expr[2]))**self.eval(expr[3]) |
| elif etype == 'VAR': |
| var, dim1, dim2 = expr[1] |
| if not dim1 and not dim2: |
| if var in self.vars: |
| return self.vars[var] |
| else: |
| print("UNDEFINED VARIABLE %s AT LINE %s" % |
| (var, self.stat[self.pc])) |
| raise RuntimeError |
| # May be a list lookup or a function evaluation |
| if dim1 and not dim2: |
| if var in self.functions: |
| # A function |
| return self.functions[var](dim1) |
| else: |
| # A list evaluation |
| if var in self.lists: |
| dim1val = self.eval(dim1) |
| if dim1val < 1 or dim1val > len(self.lists[var]): |
| print("LIST INDEX OUT OF BOUNDS AT LINE %s" % |
| self.stat[self.pc]) |
| raise RuntimeError |
| return self.lists[var][dim1val - 1] |
| if dim1 and dim2: |
| if var in self.tables: |
| dim1val = self.eval(dim1) |
| dim2val = self.eval(dim2) |
| if dim1val < 1 or dim1val > len(self.tables[var]) or dim2val < 1 or dim2val > len(self.tables[var][0]): |
| print("TABLE INDEX OUT OUT BOUNDS AT LINE %s" % |
| self.stat[self.pc]) |
| raise RuntimeError |
| return self.tables[var][dim1val - 1][dim2val - 1] |
| print("UNDEFINED VARIABLE %s AT LINE %s" % |
| (var, self.stat[self.pc])) |
| raise RuntimeError |
| |
| # Evaluate a relational expression |
| def releval(self, expr): |
| etype = expr[1] |
| lhs = self.eval(expr[2]) |
| rhs = self.eval(expr[3]) |
| if etype == '<': |
| if lhs < rhs: |
| return 1 |
| else: |
| return 0 |
| |
| elif etype == '<=': |
| if lhs <= rhs: |
| return 1 |
| else: |
| return 0 |
| |
| elif etype == '>': |
| if lhs > rhs: |
| return 1 |
| else: |
| return 0 |
| |
| elif etype == '>=': |
| if lhs >= rhs: |
| return 1 |
| else: |
| return 0 |
| |
| elif etype == '=': |
| if lhs == rhs: |
| return 1 |
| else: |
| return 0 |
| |
| elif etype == '<>': |
| if lhs != rhs: |
| return 1 |
| else: |
| return 0 |
| |
| # Assignment |
| def assign(self, target, value): |
| var, dim1, dim2 = target |
| if not dim1 and not dim2: |
| self.vars[var] = self.eval(value) |
| elif dim1 and not dim2: |
| # List assignment |
| dim1val = self.eval(dim1) |
| if not var in self.lists: |
| self.lists[var] = [0] * 10 |
| |
| if dim1val > len(self.lists[var]): |
| print ("DIMENSION TOO LARGE AT LINE %s" % self.stat[self.pc]) |
| raise RuntimeError |
| self.lists[var][dim1val - 1] = self.eval(value) |
| elif dim1 and dim2: |
| dim1val = self.eval(dim1) |
| dim2val = self.eval(dim2) |
| if not var in self.tables: |
| temp = [0] * 10 |
| v = [] |
| for i in range(10): |
| v.append(temp[:]) |
| self.tables[var] = v |
| # Variable already exists |
| if dim1val > len(self.tables[var]) or dim2val > len(self.tables[var][0]): |
| print("DIMENSION TOO LARGE AT LINE %s" % self.stat[self.pc]) |
| raise RuntimeError |
| self.tables[var][dim1val - 1][dim2val - 1] = self.eval(value) |
| |
| # Change the current line number |
| def goto(self, linenum): |
| if not linenum in self.prog: |
| print("UNDEFINED LINE NUMBER %d AT LINE %d" % |
| (linenum, self.stat[self.pc])) |
| raise RuntimeError |
| self.pc = self.stat.index(linenum) |
| |
| # Run it |
| def run(self): |
| self.vars = {} # All variables |
| self.lists = {} # List variables |
| self.tables = {} # Tables |
| self.loops = [] # Currently active loops |
| self.loopend = {} # Mapping saying where loops end |
| self.gosub = None # Gosub return point (if any) |
| self.error = 0 # Indicates program error |
| |
| self.stat = list(self.prog) # Ordered list of all line numbers |
| self.stat.sort() |
| self.pc = 0 # Current program counter |
| |
| # Processing prior to running |
| |
| self.collect_data() # Collect all of the data statements |
| self.check_end() |
| self.check_loops() |
| |
| if self.error: |
| raise RuntimeError |
| |
| while 1: |
| line = self.stat[self.pc] |
| instr = self.prog[line] |
| |
| op = instr[0] |
| |
| # END and STOP statements |
| if op == 'END' or op == 'STOP': |
| break # We're done |
| |
| # GOTO statement |
| elif op == 'GOTO': |
| newline = instr[1] |
| self.goto(newline) |
| continue |
| |
| # PRINT statement |
| elif op == 'PRINT': |
| plist = instr[1] |
| out = "" |
| for label, val in plist: |
| if out: |
| out += ' ' * (15 - (len(out) % 15)) |
| out += label |
| if val: |
| if label: |
| out += " " |
| eval = self.eval(val) |
| out += str(eval) |
| sys.stdout.write(out) |
| end = instr[2] |
| if not (end == ',' or end == ';'): |
| sys.stdout.write("\n") |
| if end == ',': |
| sys.stdout.write(" " * (15 - (len(out) % 15))) |
| if end == ';': |
| sys.stdout.write(" " * (3 - (len(out) % 3))) |
| |
| # LET statement |
| elif op == 'LET': |
| target = instr[1] |
| value = instr[2] |
| self.assign(target, value) |
| |
| # READ statement |
| elif op == 'READ': |
| for target in instr[1]: |
| if self.dc < len(self.data): |
| value = ('NUM', self.data[self.dc]) |
| self.assign(target, value) |
| self.dc += 1 |
| else: |
| # No more data. Program ends |
| return |
| elif op == 'IF': |
| relop = instr[1] |
| newline = instr[2] |
| if (self.releval(relop)): |
| self.goto(newline) |
| continue |
| |
| elif op == 'FOR': |
| loopvar = instr[1] |
| initval = instr[2] |
| finval = instr[3] |
| stepval = instr[4] |
| |
| # Check to see if this is a new loop |
| if not self.loops or self.loops[-1][0] != self.pc: |
| # Looks like a new loop. Make the initial assignment |
| newvalue = initval |
| self.assign((loopvar, None, None), initval) |
| if not stepval: |
| stepval = ('NUM', 1) |
| stepval = self.eval(stepval) # Evaluate step here |
| self.loops.append((self.pc, stepval)) |
| else: |
| # It's a repeat of the previous loop |
| # Update the value of the loop variable according to the |
| # step |
| stepval = ('NUM', self.loops[-1][1]) |
| newvalue = ( |
| 'BINOP', '+', ('VAR', (loopvar, None, None)), stepval) |
| |
| if self.loops[-1][1] < 0: |
| relop = '>=' |
| else: |
| relop = '<=' |
| if not self.releval(('RELOP', relop, newvalue, finval)): |
| # Loop is done. Jump to the NEXT |
| self.pc = self.loopend[self.pc] |
| self.loops.pop() |
| else: |
| self.assign((loopvar, None, None), newvalue) |
| |
| elif op == 'NEXT': |
| if not self.loops: |
| print("NEXT WITHOUT FOR AT LINE %s" % line) |
| return |
| |
| nextvar = instr[1] |
| self.pc = self.loops[-1][0] |
| loopinst = self.prog[self.stat[self.pc]] |
| forvar = loopinst[1] |
| if nextvar != forvar: |
| print("NEXT DOESN'T MATCH FOR AT LINE %s" % line) |
| return |
| continue |
| elif op == 'GOSUB': |
| newline = instr[1] |
| if self.gosub: |
| print("ALREADY IN A SUBROUTINE AT LINE %s" % line) |
| return |
| self.gosub = self.stat[self.pc] |
| self.goto(newline) |
| continue |
| |
| elif op == 'RETURN': |
| if not self.gosub: |
| print("RETURN WITHOUT A GOSUB AT LINE %s" % line) |
| return |
| self.goto(self.gosub) |
| self.gosub = None |
| |
| elif op == 'FUNC': |
| fname = instr[1] |
| pname = instr[2] |
| expr = instr[3] |
| |
| def eval_func(pvalue, name=pname, self=self, expr=expr): |
| self.assign((pname, None, None), pvalue) |
| return self.eval(expr) |
| self.functions[fname] = eval_func |
| |
| elif op == 'DIM': |
| for vname, x, y in instr[1]: |
| if y == 0: |
| # Single dimension variable |
| self.lists[vname] = [0] * x |
| else: |
| # Double dimension variable |
| temp = [0] * y |
| v = [] |
| for i in range(x): |
| v.append(temp[:]) |
| self.tables[vname] = v |
| |
| self.pc += 1 |
| |
| # Utility functions for program listing |
| def expr_str(self, expr): |
| etype = expr[0] |
| if etype == 'NUM': |
| return str(expr[1]) |
| elif etype == 'GROUP': |
| return "(%s)" % self.expr_str(expr[1]) |
| elif etype == 'UNARY': |
| if expr[1] == '-': |
| return "-" + str(expr[2]) |
| elif etype == 'BINOP': |
| return "%s %s %s" % (self.expr_str(expr[2]), expr[1], self.expr_str(expr[3])) |
| elif etype == 'VAR': |
| return self.var_str(expr[1]) |
| |
| def relexpr_str(self, expr): |
| return "%s %s %s" % (self.expr_str(expr[2]), expr[1], self.expr_str(expr[3])) |
| |
| def var_str(self, var): |
| varname, dim1, dim2 = var |
| if not dim1 and not dim2: |
| return varname |
| if dim1 and not dim2: |
| return "%s(%s)" % (varname, self.expr_str(dim1)) |
| return "%s(%s,%s)" % (varname, self.expr_str(dim1), self.expr_str(dim2)) |
| |
| # Create a program listing |
| def list(self): |
| stat = list(self.prog) # Ordered list of all line numbers |
| stat.sort() |
| for line in stat: |
| instr = self.prog[line] |
| op = instr[0] |
| if op in ['END', 'STOP', 'RETURN']: |
| print("%s %s" % (line, op)) |
| continue |
| elif op == 'REM': |
| print("%s %s" % (line, instr[1])) |
| elif op == 'PRINT': |
| _out = "%s %s " % (line, op) |
| first = 1 |
| for p in instr[1]: |
| if not first: |
| _out += ", " |
| if p[0] and p[1]: |
| _out += '"%s"%s' % (p[0], self.expr_str(p[1])) |
| elif p[1]: |
| _out += self.expr_str(p[1]) |
| else: |
| _out += '"%s"' % (p[0],) |
| first = 0 |
| if instr[2]: |
| _out += instr[2] |
| print(_out) |
| elif op == 'LET': |
| print("%s LET %s = %s" % |
| (line, self.var_str(instr[1]), self.expr_str(instr[2]))) |
| elif op == 'READ': |
| _out = "%s READ " % line |
| first = 1 |
| for r in instr[1]: |
| if not first: |
| _out += "," |
| _out += self.var_str(r) |
| first = 0 |
| print(_out) |
| elif op == 'IF': |
| print("%s IF %s THEN %d" % |
| (line, self.relexpr_str(instr[1]), instr[2])) |
| elif op == 'GOTO' or op == 'GOSUB': |
| print("%s %s %s" % (line, op, instr[1])) |
| elif op == 'FOR': |
| _out = "%s FOR %s = %s TO %s" % ( |
| line, instr[1], self.expr_str(instr[2]), self.expr_str(instr[3])) |
| if instr[4]: |
| _out += " STEP %s" % (self.expr_str(instr[4])) |
| print(_out) |
| elif op == 'NEXT': |
| print("%s NEXT %s" % (line, instr[1])) |
| elif op == 'FUNC': |
| print("%s DEF %s(%s) = %s" % |
| (line, instr[1], instr[2], self.expr_str(instr[3]))) |
| elif op == 'DIM': |
| _out = "%s DIM " % line |
| first = 1 |
| for vname, x, y in instr[1]: |
| if not first: |
| _out += "," |
| first = 0 |
| if y == 0: |
| _out += "%s(%d)" % (vname, x) |
| else: |
| _out += "%s(%d,%d)" % (vname, x, y) |
| |
| print(_out) |
| elif op == 'DATA': |
| _out = "%s DATA " % line |
| first = 1 |
| for v in instr[1]: |
| if not first: |
| _out += "," |
| first = 0 |
| _out += v |
| print(_out) |
| |
| # Erase the current program |
| def new(self): |
| self.prog = {} |
| |
| # Insert statements |
| def add_statements(self, prog): |
| for line, stat in prog.items(): |
| self.prog[line] = stat |
| |
| # Delete a statement |
| def del_line(self, lineno): |
| try: |
| del self.prog[lineno] |
| except KeyError: |
| pass |