view ply-3.8/example/BASIC/basinterp.py @ 9610:3b572502ce53

<oerjan> learn Dragons are fractal creatures of magic, capable of shrinking or expanding to any size. Taneb invented them to live inside his string diagrams, but they prefer to hover around pinheads and feed on angels.
author HackBot
date Wed, 02 Nov 2016 23:36:25 +0000
parents 343ff337a19b
children
line wrap: on
line source

# This file provides the runtime support for running a basic program
# Assumes the program has been parsed using basparse.py

import sys
import math
import random

class BasicInterpreter:

    # Initialize the interpreter. prog is a dictionary
    # containing (line,statement) mappings
    def __init__(self,prog):
         self.prog = prog

         self.functions = {           # Built-in function table
             'SIN' : lambda z: math.sin(self.eval(z)),
             'COS' : lambda z: math.cos(self.eval(z)),
             'TAN' : lambda z: math.tan(self.eval(z)),
             'ATN' : lambda z: math.atan(self.eval(z)),
             'EXP' : lambda z: math.exp(self.eval(z)),
             'ABS' : lambda z: abs(self.eval(z)),
             'LOG' : lambda z: math.log(self.eval(z)),
             'SQR' : lambda z: math.sqrt(self.eval(z)),
             'INT' : lambda z: int(self.eval(z)),
             'RND' : lambda z: random.random()
         }

    # Collect all data statements
    def collect_data(self):
         self.data = []
         for lineno in self.stat:
              if self.prog[lineno][0] == 'DATA':
                  self.data = self.data + self.prog[lineno][1]
         self.dc = 0                  # Initialize the data counter

    # Check for end statements
    def check_end(self):
         has_end = 0
         for lineno in self.stat:
             if self.prog[lineno][0] == 'END' and not has_end:
                  has_end = lineno
         if not has_end:
             print("NO END INSTRUCTION")
             self.error = 1
             return
         if has_end != lineno:
             print("END IS NOT LAST")
             self.error = 1

    # Check loops
    def check_loops(self):
         for pc in range(len(self.stat)):
             lineno = self.stat[pc]
             if self.prog[lineno][0] == 'FOR':
                  forinst = self.prog[lineno]
                  loopvar = forinst[1]
                  for i in range(pc+1,len(self.stat)):
                       if self.prog[self.stat[i]][0] == 'NEXT':
                            nextvar = self.prog[self.stat[i]][1]
                            if nextvar != loopvar: continue
                            self.loopend[pc] = i
                            break
                  else:
                       print("FOR WITHOUT NEXT AT LINE %s" % self.stat[pc])
                       self.error = 1
                  
    # Evaluate an expression
    def eval(self,expr):
        etype = expr[0]
        if etype == 'NUM': return expr[1]
        elif etype == 'GROUP': return self.eval(expr[1])
        elif etype == 'UNARY':
             if expr[1] == '-': return -self.eval(expr[2])
        elif etype == 'BINOP':
             if expr[1] == '+': return self.eval(expr[2])+self.eval(expr[3])
             elif expr[1] == '-': return self.eval(expr[2])-self.eval(expr[3])
             elif expr[1] == '*': return self.eval(expr[2])*self.eval(expr[3])
             elif expr[1] == '/': return float(self.eval(expr[2]))/self.eval(expr[3])
             elif expr[1] == '^': return abs(self.eval(expr[2]))**self.eval(expr[3])
        elif etype == 'VAR':
             var,dim1,dim2 = expr[1]
             if not dim1 and not dim2:
                  if var in self.vars:
                       return self.vars[var]
                  else:
                       print("UNDEFINED VARIABLE %s AT LINE %s" % (var, self.stat[self.pc]))
                       raise RuntimeError
             # May be a list lookup or a function evaluation
             if dim1 and not dim2:
                if var in self.functions:
                      # A function
                      return self.functions[var](dim1)
                else:
                      # A list evaluation
                      if var in self.lists:
                            dim1val = self.eval(dim1)
                            if dim1val < 1 or dim1val > len(self.lists[var]):
                                 print("LIST INDEX OUT OF BOUNDS AT LINE %s" % self.stat[self.pc])
                                 raise RuntimeError
                            return self.lists[var][dim1val-1]
             if dim1 and dim2:
                 if var in self.tables:
                      dim1val = self.eval(dim1)
                      dim2val = self.eval(dim2)
                      if dim1val < 1 or dim1val > len(self.tables[var]) or dim2val < 1 or dim2val > len(self.tables[var][0]):
                           print("TABLE INDEX OUT OUT BOUNDS AT LINE %s" % self.stat[self.pc])
                           raise RuntimeError
                      return self.tables[var][dim1val-1][dim2val-1]
             print("UNDEFINED VARIABLE %s AT LINE %s" % (var, self.stat[self.pc]))
             raise RuntimeError

    # Evaluate a relational expression
    def releval(self,expr):
         etype = expr[1]
         lhs   = self.eval(expr[2])
         rhs   = self.eval(expr[3])
         if etype == '<':
             if lhs < rhs: return 1
             else: return 0

         elif etype == '<=':
             if lhs <= rhs: return 1
             else: return 0

         elif etype == '>':
             if lhs > rhs: return 1
             else: return 0

         elif etype == '>=':
             if lhs >= rhs: return 1
             else: return 0

         elif etype == '=':
             if lhs == rhs: return 1
             else: return 0

         elif etype == '<>':
             if lhs != rhs: return 1
             else: return 0

    # Assignment
    def assign(self,target,value):
        var, dim1, dim2 = target
        if not dim1 and not dim2:
            self.vars[var] = self.eval(value)
        elif dim1 and not dim2:
            # List assignment
            dim1val = self.eval(dim1)
            if not var in self.lists:
                 self.lists[var] = [0]*10

            if dim1val > len(self.lists[var]):
                 print ("DIMENSION TOO LARGE AT LINE %s" % self.stat[self.pc])
                 raise RuntimeError
            self.lists[var][dim1val-1] = self.eval(value)
        elif dim1 and dim2:
            dim1val = self.eval(dim1)
            dim2val = self.eval(dim2)
            if not var in self.tables:
                 temp = [0]*10
                 v = []
                 for i in range(10): v.append(temp[:])
                 self.tables[var] = v
            # Variable already exists
            if dim1val > len(self.tables[var]) or dim2val > len(self.tables[var][0]):
                 print("DIMENSION TOO LARGE AT LINE %s" % self.stat[self.pc])
                 raise RuntimeError
            self.tables[var][dim1val-1][dim2val-1] = self.eval(value)

    # Change the current line number
    def goto(self,linenum):
         if not linenum in self.prog:
              print("UNDEFINED LINE NUMBER %d AT LINE %d" % (linenum, self.stat[self.pc]))
              raise RuntimeError
         self.pc = self.stat.index(linenum)

    # Run it
    def run(self):
        self.vars   = { }            # All variables
        self.lists  = { }            # List variables
        self.tables = { }            # Tables
        self.loops  = [ ]            # Currently active loops
        self.loopend= { }            # Mapping saying where loops end
        self.gosub  = None           # Gosub return point (if any)
        self.error  = 0              # Indicates program error

        self.stat = list(self.prog)  # Ordered list of all line numbers
        self.stat.sort()
        self.pc = 0                  # Current program counter

        # Processing prior to running

        self.collect_data()          # Collect all of the data statements
        self.check_end()
        self.check_loops()

        if self.error: raise RuntimeError

        while 1:
            line  = self.stat[self.pc]
            instr = self.prog[line]
            
            op = instr[0]

            # END and STOP statements
            if op == 'END' or op == 'STOP':
                 break           # We're done

            # GOTO statement
            elif op == 'GOTO':
                 newline = instr[1]
                 self.goto(newline)
                 continue

            # PRINT statement
            elif op == 'PRINT':
                 plist = instr[1]
                 out = ""
                 for label,val in plist:
                     if out:
                          out += ' '*(15 - (len(out) % 15))
                     out += label
                     if val:
                          if label: out += " "
                          eval = self.eval(val)
                          out += str(eval)
                 sys.stdout.write(out)
                 end = instr[2]
                 if not (end == ',' or end == ';'): 
                     sys.stdout.write("\n")
                 if end == ',': sys.stdout.write(" "*(15-(len(out) % 15)))
                 if end == ';': sys.stdout.write(" "*(3-(len(out) % 3)))
                     
            # LET statement
            elif op == 'LET':
                 target = instr[1]
                 value  = instr[2]
                 self.assign(target,value)

            # READ statement
            elif op == 'READ':
                 for target in instr[1]:
                      if self.dc < len(self.data):
                          value = ('NUM',self.data[self.dc])
                          self.assign(target,value)
                          self.dc += 1
                      else:
                          # No more data.  Program ends
                          return
            elif op == 'IF':
                 relop = instr[1]
                 newline = instr[2]
                 if (self.releval(relop)):
                     self.goto(newline)
                     continue

            elif op == 'FOR':
                 loopvar = instr[1]
                 initval = instr[2]
                 finval  = instr[3]
                 stepval = instr[4]
              
                 # Check to see if this is a new loop
                 if not self.loops or self.loops[-1][0] != self.pc:
                        # Looks like a new loop. Make the initial assignment
                        newvalue = initval
                        self.assign((loopvar,None,None),initval)
                        if not stepval: stepval = ('NUM',1)
                        stepval = self.eval(stepval)    # Evaluate step here
                        self.loops.append((self.pc,stepval))
                 else:
                        # It's a repeat of the previous loop
                        # Update the value of the loop variable according to the step
                        stepval = ('NUM',self.loops[-1][1])
                        newvalue = ('BINOP','+',('VAR',(loopvar,None,None)),stepval)

                 if self.loops[-1][1] < 0: relop = '>='
                 else: relop = '<='
                 if not self.releval(('RELOP',relop,newvalue,finval)):
                      # Loop is done. Jump to the NEXT
                      self.pc = self.loopend[self.pc]
                      self.loops.pop()
                 else:
                      self.assign((loopvar,None,None),newvalue)

            elif op == 'NEXT':
                 if not self.loops:
                       print("NEXT WITHOUT FOR AT LINE %s" % line)
                       return
 
                 nextvar = instr[1]
                 self.pc = self.loops[-1][0]
                 loopinst = self.prog[self.stat[self.pc]]
                 forvar = loopinst[1]
                 if nextvar != forvar:
                       print("NEXT DOESN'T MATCH FOR AT LINE %s" % line)
                       return
                 continue
            elif op == 'GOSUB':
                 newline = instr[1]
                 if self.gosub:
                       print("ALREADY IN A SUBROUTINE AT LINE %s" % line)
                       return
                 self.gosub = self.stat[self.pc]
                 self.goto(newline)
                 continue

            elif op == 'RETURN':
                 if not self.gosub:
                      print("RETURN WITHOUT A GOSUB AT LINE %s" % line)
                      return
                 self.goto(self.gosub)
                 self.gosub = None

            elif op == 'FUNC':
                 fname = instr[1]
                 pname = instr[2]
                 expr  = instr[3]
                 def eval_func(pvalue,name=pname,self=self,expr=expr):
                      self.assign((pname,None,None),pvalue)
                      return self.eval(expr)
                 self.functions[fname] = eval_func

            elif op == 'DIM':
                 for vname,x,y in instr[1]:
                     if y == 0:
                          # Single dimension variable
                          self.lists[vname] = [0]*x
                     else:
                          # Double dimension variable
                          temp = [0]*y
                          v = []
                          for i in range(x):
                              v.append(temp[:])
                          self.tables[vname] = v

            self.pc += 1         

    # Utility functions for program listing
    def expr_str(self,expr):
        etype = expr[0]
        if etype == 'NUM': return str(expr[1])
        elif etype == 'GROUP': return "(%s)" % self.expr_str(expr[1])
        elif etype == 'UNARY':
             if expr[1] == '-': return "-"+str(expr[2])
        elif etype == 'BINOP':
             return "%s %s %s" % (self.expr_str(expr[2]),expr[1],self.expr_str(expr[3]))
        elif etype == 'VAR':
              return self.var_str(expr[1])

    def relexpr_str(self,expr):
         return "%s %s %s" % (self.expr_str(expr[2]),expr[1],self.expr_str(expr[3]))

    def var_str(self,var):
         varname,dim1,dim2 = var
         if not dim1 and not dim2: return varname
         if dim1 and not dim2: return "%s(%s)" % (varname, self.expr_str(dim1))
         return "%s(%s,%s)" % (varname, self.expr_str(dim1),self.expr_str(dim2))

    # Create a program listing
    def list(self):
         stat = list(self.prog)      # Ordered list of all line numbers
         stat.sort()
         for line in stat:
             instr = self.prog[line]
             op = instr[0]
             if op in ['END','STOP','RETURN']:
                   print("%s %s" % (line, op))
                   continue
             elif op == 'REM':
                   print("%s %s" % (line, instr[1]))
             elif op == 'PRINT':
                   _out = "%s %s " % (line, op)
                   first = 1
                   for p in instr[1]:
                         if not first: _out += ", "
                         if p[0] and p[1]: _out += '"%s"%s' % (p[0],self.expr_str(p[1]))
                         elif p[1]: _out += self.expr_str(p[1])
                         else: _out += '"%s"' % (p[0],)
                         first = 0
                   if instr[2]: _out += instr[2]
                   print(_out)
             elif op == 'LET':
                   print("%s LET %s = %s" % (line,self.var_str(instr[1]),self.expr_str(instr[2])))
             elif op == 'READ':
                   _out = "%s READ " % line
                   first = 1
                   for r in instr[1]:
                         if not first: _out += ","
                         _out += self.var_str(r)
                         first = 0
                   print(_out)
             elif op == 'IF':
                   print("%s IF %s THEN %d" % (line,self.relexpr_str(instr[1]),instr[2]))
             elif op == 'GOTO' or op == 'GOSUB':
                   print("%s %s %s" % (line, op, instr[1]))
             elif op == 'FOR':
                   _out = "%s FOR %s = %s TO %s" % (line,instr[1],self.expr_str(instr[2]),self.expr_str(instr[3]))
                   if instr[4]: _out += " STEP %s" % (self.expr_str(instr[4]))
                   print(_out)
             elif op == 'NEXT':
                   print("%s NEXT %s" % (line, instr[1]))
             elif op == 'FUNC':
                   print("%s DEF %s(%s) = %s" % (line,instr[1],instr[2],self.expr_str(instr[3])))
             elif op == 'DIM':
                   _out = "%s DIM " % line
                   first = 1
                   for vname,x,y in instr[1]:
                         if not first: _out += ","
                         first = 0
                         if y == 0:
                               _out += "%s(%d)" % (vname,x)
                         else:
                               _out += "%s(%d,%d)" % (vname,x,y)
                         
                   print(_out)
             elif op == 'DATA':
                   _out = "%s DATA " % line
                   first = 1
                   for v in instr[1]:
                        if not first: _out += ","
                        first = 0
                        _out += v
                   print(_out)

    # Erase the current program
    def new(self):
         self.prog = {}
 
    # Insert statements
    def add_statements(self,prog):
         for line,stat in prog.items():
              self.prog[line] = stat

    # Delete a statement
    def del_line(self,lineno):
         try:
             del self.prog[lineno]
         except KeyError:
             pass