1 # This file provides the runtime support for running a basic program
2 # Assumes the program has been parsed using basparse.py
8 class BasicInterpreter
:
10 # Initialize the interpreter. prog is a dictionary
11 # containing (line,statement) mappings
12 def __init__(self
,prog
):
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()
28 # Collect all data statements
29 def collect_data(self
):
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
36 # Check for end statements
39 for lineno
in self
.stat
:
40 if self
.prog
[lineno
][0] == 'END' and not has_end
:
43 print "NO END INSTRUCTION"
46 print "END IS NOT LAST"
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
]
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
63 print "FOR WITHOUT NEXT AT LINE" % self
.stat
[pc
]
66 # Evaluate an expression
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])
80 var
,dim1
,dim2
= expr
[1]
81 if not dim1
and not dim2
:
82 if self
.vars.has_key(var
):
85 print "UNDEFINED VARIABLE", var
, "AT LINE", self
.stat
[self
.pc
]
87 # May be a list lookup or a function evaluation
89 if self
.functions
.has_key(var
):
91 return self
.functions
[var
](dim1
)
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
]
99 return self
.lists
[var
][dim1val
-1]
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
]
107 return self
.tables
[var
][dim1val
-1][dim2val
-1]
108 print "UNDEFINED VARIABLE", var
, "AT LINE", self
.stat
[self
.pc
]
111 # Evaluate a relational expression
112 def releval(self
,expr
):
114 lhs
= self
.eval(expr
[2])
115 rhs
= self
.eval(expr
[3])
117 if lhs
< rhs
: return 1
121 if lhs
<= rhs
: return 1
125 if lhs
> rhs
: return 1
129 if lhs
>= rhs
: return 1
133 if lhs
== rhs
: return 1
137 if lhs
!= rhs
: return 1
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
:
147 dim1val
= self
.eval(dim1
)
148 if not self
.lists
.has_key(var
):
149 self
.lists
[var
] = [0]*10
151 if dim1val
> len(self
.lists
[var
]):
152 print "DIMENSION TOO LARGE AT LINE", self
.stat
[self
.pc
]
154 self
.lists
[var
][dim1val
-1] = self
.eval(value
)
156 dim1val
= self
.eval(dim1
)
157 dim2val
= self
.eval(dim2
)
158 if not self
.tables
.has_key(var
):
161 for i
in range(10): v
.append(temp
[:])
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
]
167 self
.tables
[var
][dim1val
-1][dim2val
-1] = self
.eval(value
)
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
])
174 self
.pc
= self
.stat
.index(linenum
)
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
186 self
.stat
= self
.prog
.keys() # Ordered list of all line numbers
188 self
.pc
= 0 # Current program counter
190 # Processing prior to running
192 self
.collect_data() # Collect all of the data statements
196 if self
.error
: raise RuntimeError
199 line
= self
.stat
[self
.pc
]
200 instr
= self
.prog
[line
]
204 # END and STOP statements
205 if op
== 'END' or op
== 'STOP':
218 for label
,val
in plist
:
220 out
+= ' '*(15 - (len(out
) % 15))
224 eval = self
.eval(val
)
226 sys
.stdout
.write(out
)
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)))
237 self
.assign(target
,value
)
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
)
247 # No more data. Program ends
252 if (self
.releval(relop
)):
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
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
))
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
)
276 if self
.loops
[-1][1] < 0: 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
]
283 self
.assign((loopvar
,None,None),newvalue
)
287 print "NEXT WITHOUT FOR AT LINE",line
291 self
.pc
= self
.loops
[-1][0]
292 loopinst
= self
.prog
[self
.stat
[self
.pc
]]
294 if nextvar
!= forvar
:
295 print "NEXT DOESN'T MATCH FOR AT LINE", line
301 print "ALREADY IN A SUBROUTINE AT LINE", line
303 self
.gosub
= self
.stat
[self
.pc
]
309 print "RETURN WITHOUT A GOSUB AT LINE",line
311 self
.goto(self
.gosub
)
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
324 for vname
,x
,y
in instr
[1]:
326 # Single dimension variable
327 self
.lists
[vname
] = [0]*x
329 # Double dimension variable
334 self
.tables
[vname
] = v
338 # Utility functions for program listing
339 def expr_str(self
,expr
):
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]))
348 return self
.var_str(expr
[1])
350 def relexpr_str(self
,expr
):
351 return "%s %s %s" % (self
.expr_str(expr
[2]),expr
[1],self
.expr_str(expr
[3]))
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
))
359 # Create a program listing
361 stat
= self
.prog
.keys() # Ordered list of all line numbers
364 instr
= self
.prog
[line
]
366 if op
in ['END','STOP','RETURN']:
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],),
380 if instr
[2]: print instr
[2]
383 print line
,"LET",self
.var_str(instr
[1]),"=",self
.expr_str(instr
[2])
388 if not first
: print ",",
389 print self
.var_str(r
),
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]
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])),
401 print line
,"NEXT", instr
[1]
403 print line
,"DEF %s(%s) = %s" % (instr
[1],instr
[2],self
.expr_str(instr
[3]))
407 for vname
,x
,y
in instr
[1]:
408 if not first
: print ",",
411 print "%s(%d)" % (vname
,x
),
413 print "%s(%d,%d)" % (vname
,x
,y
),
420 if not first
: print ",",
425 # Erase the current program
430 def add_statements(self
,prog
):
431 for line
,stat
in prog
.items():
432 self
.prog
[line
] = stat
435 def del_line(self
,lineno
):
437 del self
.prog
[lineno
]