7267
|
1 # This file provides the runtime support for running a basic program
|
|
2 # Assumes the program has been parsed using basparse.py
|
|
3
|
|
4 import sys
|
|
5 import math
|
|
6 import random
|
|
7
|
|
8 class 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
|