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