• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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