Home | History | Annotate | Download | only in BASIC
      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 
      9 class 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