cscg24-lolpython

CSCG 2024 Challenge 'Can I Haz Lolpython?'
git clone https://git.sinitax.com/sinitax/cscg24-lolpython
Log | Files | Refs | sfeed.txt

basinterp.py (17106B)


      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