1# This file provides the runtime support for running a basic program 2# Assumes the program has been parsed using basparse.py 3 4import sys 5import math 6import random 7 8 9class BasicInterpreter: 10 11 # Initialize the interpreter. prog is a dictionary 12 # containing (line,statement) mappings 13 def __init__(self, prog): 14 self.prog = prog 15 16 self.functions = { # Built-in function table 17 'SIN': lambda z: math.sin(self.eval(z)), 18 'COS': lambda z: math.cos(self.eval(z)), 19 'TAN': lambda z: math.tan(self.eval(z)), 20 'ATN': lambda z: math.atan(self.eval(z)), 21 'EXP': lambda z: math.exp(self.eval(z)), 22 'ABS': lambda z: abs(self.eval(z)), 23 'LOG': lambda z: math.log(self.eval(z)), 24 'SQR': lambda z: math.sqrt(self.eval(z)), 25 'INT': lambda z: int(self.eval(z)), 26 'RND': lambda z: random.random() 27 } 28 29 # Collect all data statements 30 def collect_data(self): 31 self.data = [] 32 for lineno in self.stat: 33 if self.prog[lineno][0] == 'DATA': 34 self.data = self.data + self.prog[lineno][1] 35 self.dc = 0 # Initialize the data counter 36 37 # Check for end statements 38 def check_end(self): 39 has_end = 0 40 for lineno in self.stat: 41 if self.prog[lineno][0] == 'END' and not has_end: 42 has_end = lineno 43 if not has_end: 44 print("NO END INSTRUCTION") 45 self.error = 1 46 return 47 if has_end != lineno: 48 print("END IS NOT LAST") 49 self.error = 1 50 51 # Check loops 52 def check_loops(self): 53 for pc in range(len(self.stat)): 54 lineno = self.stat[pc] 55 if self.prog[lineno][0] == 'FOR': 56 forinst = self.prog[lineno] 57 loopvar = forinst[1] 58 for i in range(pc + 1, len(self.stat)): 59 if self.prog[self.stat[i]][0] == 'NEXT': 60 nextvar = self.prog[self.stat[i]][1] 61 if nextvar != loopvar: 62 continue 63 self.loopend[pc] = i 64 break 65 else: 66 print("FOR WITHOUT NEXT AT LINE %s" % self.stat[pc]) 67 self.error = 1 68 69 # Evaluate an expression 70 def eval(self, expr): 71 etype = expr[0] 72 if etype == 'NUM': 73 return expr[1] 74 elif etype == 'GROUP': 75 return self.eval(expr[1]) 76 elif etype == 'UNARY': 77 if expr[1] == '-': 78 return -self.eval(expr[2]) 79 elif etype == 'BINOP': 80 if expr[1] == '+': 81 return self.eval(expr[2]) + self.eval(expr[3]) 82 elif expr[1] == '-': 83 return self.eval(expr[2]) - self.eval(expr[3]) 84 elif expr[1] == '*': 85 return self.eval(expr[2]) * self.eval(expr[3]) 86 elif expr[1] == '/': 87 return float(self.eval(expr[2])) / self.eval(expr[3]) 88 elif expr[1] == '^': 89 return abs(self.eval(expr[2]))**self.eval(expr[3]) 90 elif etype == 'VAR': 91 var, dim1, dim2 = expr[1] 92 if not dim1 and not dim2: 93 if var in self.vars: 94 return self.vars[var] 95 else: 96 print("UNDEFINED VARIABLE %s AT LINE %s" % 97 (var, self.stat[self.pc])) 98 raise RuntimeError 99 # May be a list lookup or a function evaluation 100 if dim1 and not dim2: 101 if var in self.functions: 102 # A function 103 return self.functions[var](dim1) 104 else: 105 # A list evaluation 106 if var in self.lists: 107 dim1val = self.eval(dim1) 108 if dim1val < 1 or dim1val > len(self.lists[var]): 109 print("LIST INDEX OUT OF BOUNDS AT LINE %s" % 110 self.stat[self.pc]) 111 raise RuntimeError 112 return self.lists[var][dim1val - 1] 113 if dim1 and dim2: 114 if var in self.tables: 115 dim1val = self.eval(dim1) 116 dim2val = self.eval(dim2) 117 if dim1val < 1 or dim1val > len(self.tables[var]) or dim2val < 1 or dim2val > len(self.tables[var][0]): 118 print("TABLE INDEX OUT OUT BOUNDS AT LINE %s" % 119 self.stat[self.pc]) 120 raise RuntimeError 121 return self.tables[var][dim1val - 1][dim2val - 1] 122 print("UNDEFINED VARIABLE %s AT LINE %s" % 123 (var, self.stat[self.pc])) 124 raise RuntimeError 125 126 # Evaluate a relational expression 127 def releval(self, expr): 128 etype = expr[1] 129 lhs = self.eval(expr[2]) 130 rhs = self.eval(expr[3]) 131 if etype == '<': 132 if lhs < rhs: 133 return 1 134 else: 135 return 0 136 137 elif etype == '<=': 138 if lhs <= rhs: 139 return 1 140 else: 141 return 0 142 143 elif etype == '>': 144 if lhs > rhs: 145 return 1 146 else: 147 return 0 148 149 elif etype == '>=': 150 if lhs >= rhs: 151 return 1 152 else: 153 return 0 154 155 elif etype == '=': 156 if lhs == rhs: 157 return 1 158 else: 159 return 0 160 161 elif etype == '<>': 162 if lhs != rhs: 163 return 1 164 else: 165 return 0 166 167 # Assignment 168 def assign(self, target, value): 169 var, dim1, dim2 = target 170 if not dim1 and not dim2: 171 self.vars[var] = self.eval(value) 172 elif dim1 and not dim2: 173 # List assignment 174 dim1val = self.eval(dim1) 175 if not var in self.lists: 176 self.lists[var] = [0] * 10 177 178 if dim1val > len(self.lists[var]): 179 print ("DIMENSION TOO LARGE AT LINE %s" % self.stat[self.pc]) 180 raise RuntimeError 181 self.lists[var][dim1val - 1] = self.eval(value) 182 elif dim1 and dim2: 183 dim1val = self.eval(dim1) 184 dim2val = self.eval(dim2) 185 if not var in self.tables: 186 temp = [0] * 10 187 v = [] 188 for i in range(10): 189 v.append(temp[:]) 190 self.tables[var] = v 191 # Variable already exists 192 if dim1val > len(self.tables[var]) or dim2val > len(self.tables[var][0]): 193 print("DIMENSION TOO LARGE AT LINE %s" % self.stat[self.pc]) 194 raise RuntimeError 195 self.tables[var][dim1val - 1][dim2val - 1] = self.eval(value) 196 197 # Change the current line number 198 def goto(self, linenum): 199 if not linenum in self.prog: 200 print("UNDEFINED LINE NUMBER %d AT LINE %d" % 201 (linenum, self.stat[self.pc])) 202 raise RuntimeError 203 self.pc = self.stat.index(linenum) 204 205 # Run it 206 def run(self): 207 self.vars = {} # All variables 208 self.lists = {} # List variables 209 self.tables = {} # Tables 210 self.loops = [] # Currently active loops 211 self.loopend = {} # Mapping saying where loops end 212 self.gosub = None # Gosub return point (if any) 213 self.error = 0 # Indicates program error 214 215 self.stat = list(self.prog) # Ordered list of all line numbers 216 self.stat.sort() 217 self.pc = 0 # Current program counter 218 219 # Processing prior to running 220 221 self.collect_data() # Collect all of the data statements 222 self.check_end() 223 self.check_loops() 224 225 if self.error: 226 raise RuntimeError 227 228 while 1: 229 line = self.stat[self.pc] 230 instr = self.prog[line] 231 232 op = instr[0] 233 234 # END and STOP statements 235 if op == 'END' or op == 'STOP': 236 break # We're done 237 238 # GOTO statement 239 elif op == 'GOTO': 240 newline = instr[1] 241 self.goto(newline) 242 continue 243 244 # PRINT statement 245 elif op == 'PRINT': 246 plist = instr[1] 247 out = "" 248 for label, val in plist: 249 if out: 250 out += ' ' * (15 - (len(out) % 15)) 251 out += label 252 if val: 253 if label: 254 out += " " 255 eval = self.eval(val) 256 out += str(eval) 257 sys.stdout.write(out) 258 end = instr[2] 259 if not (end == ',' or end == ';'): 260 sys.stdout.write("\n") 261 if end == ',': 262 sys.stdout.write(" " * (15 - (len(out) % 15))) 263 if end == ';': 264 sys.stdout.write(" " * (3 - (len(out) % 3))) 265 266 # LET statement 267 elif op == 'LET': 268 target = instr[1] 269 value = instr[2] 270 self.assign(target, value) 271 272 # READ statement 273 elif op == 'READ': 274 for target in instr[1]: 275 if self.dc < len(self.data): 276 value = ('NUM', self.data[self.dc]) 277 self.assign(target, value) 278 self.dc += 1 279 else: 280 # No more data. Program ends 281 return 282 elif op == 'IF': 283 relop = instr[1] 284 newline = instr[2] 285 if (self.releval(relop)): 286 self.goto(newline) 287 continue 288 289 elif op == 'FOR': 290 loopvar = instr[1] 291 initval = instr[2] 292 finval = instr[3] 293 stepval = instr[4] 294 295 # Check to see if this is a new loop 296 if not self.loops or self.loops[-1][0] != self.pc: 297 # Looks like a new loop. Make the initial assignment 298 newvalue = initval 299 self.assign((loopvar, None, None), initval) 300 if not stepval: 301 stepval = ('NUM', 1) 302 stepval = self.eval(stepval) # Evaluate step here 303 self.loops.append((self.pc, stepval)) 304 else: 305 # It's a repeat of the previous loop 306 # Update the value of the loop variable according to the 307 # step 308 stepval = ('NUM', self.loops[-1][1]) 309 newvalue = ( 310 'BINOP', '+', ('VAR', (loopvar, None, None)), stepval) 311 312 if self.loops[-1][1] < 0: 313 relop = '>=' 314 else: 315 relop = '<=' 316 if not self.releval(('RELOP', relop, newvalue, finval)): 317 # Loop is done. Jump to the NEXT 318 self.pc = self.loopend[self.pc] 319 self.loops.pop() 320 else: 321 self.assign((loopvar, None, None), newvalue) 322 323 elif op == 'NEXT': 324 if not self.loops: 325 print("NEXT WITHOUT FOR AT LINE %s" % line) 326 return 327 328 nextvar = instr[1] 329 self.pc = self.loops[-1][0] 330 loopinst = self.prog[self.stat[self.pc]] 331 forvar = loopinst[1] 332 if nextvar != forvar: 333 print("NEXT DOESN'T MATCH FOR AT LINE %s" % line) 334 return 335 continue 336 elif op == 'GOSUB': 337 newline = instr[1] 338 if self.gosub: 339 print("ALREADY IN A SUBROUTINE AT LINE %s" % line) 340 return 341 self.gosub = self.stat[self.pc] 342 self.goto(newline) 343 continue 344 345 elif op == 'RETURN': 346 if not self.gosub: 347 print("RETURN WITHOUT A GOSUB AT LINE %s" % line) 348 return 349 self.goto(self.gosub) 350 self.gosub = None 351 352 elif op == 'FUNC': 353 fname = instr[1] 354 pname = instr[2] 355 expr = instr[3] 356 357 def eval_func(pvalue, name=pname, self=self, expr=expr): 358 self.assign((pname, None, None), pvalue) 359 return self.eval(expr) 360 self.functions[fname] = eval_func 361 362 elif op == 'DIM': 363 for vname, x, y in instr[1]: 364 if y == 0: 365 # Single dimension variable 366 self.lists[vname] = [0] * x 367 else: 368 # Double dimension variable 369 temp = [0] * y 370 v = [] 371 for i in range(x): 372 v.append(temp[:]) 373 self.tables[vname] = v 374 375 self.pc += 1 376 377 # Utility functions for program listing 378 def expr_str(self, expr): 379 etype = expr[0] 380 if etype == 'NUM': 381 return str(expr[1]) 382 elif etype == 'GROUP': 383 return "(%s)" % self.expr_str(expr[1]) 384 elif etype == 'UNARY': 385 if expr[1] == '-': 386 return "-" + str(expr[2]) 387 elif etype == 'BINOP': 388 return "%s %s %s" % (self.expr_str(expr[2]), expr[1], self.expr_str(expr[3])) 389 elif etype == 'VAR': 390 return self.var_str(expr[1]) 391 392 def relexpr_str(self, expr): 393 return "%s %s %s" % (self.expr_str(expr[2]), expr[1], self.expr_str(expr[3])) 394 395 def var_str(self, var): 396 varname, dim1, dim2 = var 397 if not dim1 and not dim2: 398 return varname 399 if dim1 and not dim2: 400 return "%s(%s)" % (varname, self.expr_str(dim1)) 401 return "%s(%s,%s)" % (varname, self.expr_str(dim1), self.expr_str(dim2)) 402 403 # Create a program listing 404 def list(self): 405 stat = list(self.prog) # Ordered list of all line numbers 406 stat.sort() 407 for line in stat: 408 instr = self.prog[line] 409 op = instr[0] 410 if op in ['END', 'STOP', 'RETURN']: 411 print("%s %s" % (line, op)) 412 continue 413 elif op == 'REM': 414 print("%s %s" % (line, instr[1])) 415 elif op == 'PRINT': 416 _out = "%s %s " % (line, op) 417 first = 1 418 for p in instr[1]: 419 if not first: 420 _out += ", " 421 if p[0] and p[1]: 422 _out += '"%s"%s' % (p[0], self.expr_str(p[1])) 423 elif p[1]: 424 _out += self.expr_str(p[1]) 425 else: 426 _out += '"%s"' % (p[0],) 427 first = 0 428 if instr[2]: 429 _out += instr[2] 430 print(_out) 431 elif op == 'LET': 432 print("%s LET %s = %s" % 433 (line, self.var_str(instr[1]), self.expr_str(instr[2]))) 434 elif op == 'READ': 435 _out = "%s READ " % line 436 first = 1 437 for r in instr[1]: 438 if not first: 439 _out += "," 440 _out += self.var_str(r) 441 first = 0 442 print(_out) 443 elif op == 'IF': 444 print("%s IF %s THEN %d" % 445 (line, self.relexpr_str(instr[1]), instr[2])) 446 elif op == 'GOTO' or op == 'GOSUB': 447 print("%s %s %s" % (line, op, instr[1])) 448 elif op == 'FOR': 449 _out = "%s FOR %s = %s TO %s" % ( 450 line, instr[1], self.expr_str(instr[2]), self.expr_str(instr[3])) 451 if instr[4]: 452 _out += " STEP %s" % (self.expr_str(instr[4])) 453 print(_out) 454 elif op == 'NEXT': 455 print("%s NEXT %s" % (line, instr[1])) 456 elif op == 'FUNC': 457 print("%s DEF %s(%s) = %s" % 458 (line, instr[1], instr[2], self.expr_str(instr[3]))) 459 elif op == 'DIM': 460 _out = "%s DIM " % line 461 first = 1 462 for vname, x, y in instr[1]: 463 if not first: 464 _out += "," 465 first = 0 466 if y == 0: 467 _out += "%s(%d)" % (vname, x) 468 else: 469 _out += "%s(%d,%d)" % (vname, x, y) 470 471 print(_out) 472 elif op == 'DATA': 473 _out = "%s DATA " % line 474 first = 1 475 for v in instr[1]: 476 if not first: 477 _out += "," 478 first = 0 479 _out += v 480 print(_out) 481 482 # Erase the current program 483 def new(self): 484 self.prog = {} 485 486 # Insert statements 487 def add_statements(self, prog): 488 for line, stat in prog.items(): 489 self.prog[line] = stat 490 491 # Delete a statement 492 def del_line(self, lineno): 493 try: 494 del self.prog[lineno] 495 except KeyError: 496 pass 497