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