micropython

rsvp.py

135:eb01c567dfeb
2008-08-27 Paul Boddie Added descendants (and self) to the collection of attributes for each class, stored in the object table. Switched the "validator" stored in the tables to be the attribute index as opposed to the offset - this might help when eventually trying to encode and store such data in low memory environments. Employed a slightly better approach to populating the displacement lists generated for tables by adding rows in descending order of utilisation (ie. with most gaps or empty entries last). Added a simple visualisation function in the test program for viewing raw table data.
     1 #!/usr/bin/env python     2      3 """     4 A really simple virtual processor employing a simple set of instructions which     5 ignore low-level operations and merely concentrate on variable access, structure     6 access, structure allocation and function invocations.     7      8 Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk>     9     10 This program is free software; you can redistribute it and/or modify it under    11 the terms of the GNU General Public License as published by the Free Software    12 Foundation; either version 3 of the License, or (at your option) any later    13 version.    14     15 This program is distributed in the hope that it will be useful, but WITHOUT    16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    17 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    18 details.    19     20 You should have received a copy of the GNU General Public License along with    21 this program.  If not, see <http://www.gnu.org/licenses/>.    22     23 --------    24     25 The execution model of the virtual processor involves the following things:    26     27   * Memory    28   * PC (program counter) stack    29   * Frame stack (containing invocation frames in use and in preparation plus    30                 temporary storage)    31   * Local frame pointer stack    32   * Invocation frame pointer stack    33   * Exception handler stack    34   * Registers: current value, boolean status value, source value, result,    35                current exception, current callable    36     37 The memory contains constants, global variable references and program code.    38     39 The PC stack contains the return address associated with each function    40 invocation.    41     42 The frame pointer stack tracks the position of frames within the frame stack.    43 """    44     45 class IllegalInstruction(Exception):    46     pass    47     48 class IllegalAddress(Exception):    49     pass    50     51 class EmptyPCStack(Exception):    52     pass    53     54 class EmptyFrameStack(Exception):    55     pass    56     57 class RSVPMachine:    58     59     "A really simple virtual processor."    60     61     def __init__(self, memory, objtable, paramtable, pc=None, debug=0):    62     63         """    64         Initialise the processor with a 'memory' (a list of values containing    65         instructions and data) and the optional program counter 'pc'.    66         """    67     68         self.memory = memory    69         self.objtable = objtable    70         self.paramtable = paramtable    71         self.pc = pc or 0    72         self.debug = debug    73     74         # Stacks.    75     76         self.pc_stack = []    77         self.frame_stack = []    78         self.local_sp_stack = []    79         self.invocation_sp_stack = []    80         self.handler_stack = []    81     82         # Registers.    83     84         self.instruction = None    85         self.operand = None    86         self.value = None    87         self.status = None    88         self.source = None    89         self.callable = None    90         self.result = None    91         self.exception = None    92     93     def load(self, address):    94     95         "Return the value at the given 'address'."    96     97         try:    98             return self.memory[address]    99         except IndexError:   100             raise IllegalAddress, address   101    102     def save(self, address, value):   103    104         "Save to the given 'address' the specified 'value'."   105    106         try:   107             self.memory[address] = value   108         except IndexError:   109             raise IllegalAddress, address   110    111     def new(self, size):   112    113         """   114         Allocate space of the given 'size', returning the address of the space.   115         """   116    117         addr = len(self.memory)   118         for i in range(0, size):   119             self.memory.append(None)   120         return addr   121    122     def push_pc(self, data):   123    124         "Push 'data' onto the PC stack."   125    126         self.pc_stack.append(data)   127    128     def pull_pc(self):   129    130         "Pull a value from the PC stack and return it."   131    132         try:   133             return self.pc_stack.pop()   134         except IndexError:   135             raise EmptyPCStack   136    137     def run(self):   138    139         "Execute code in the memory, starting from the current PC address."   140    141         try:   142             while 1:   143                 self.execute()   144         except EmptyPCStack:   145             pass   146    147     def execute(self):   148    149         "Execute code in the memory at the current PC address."   150    151         self.instruction = self.load(self.pc)   152         self.operand = self.instruction.get_operand()   153    154         instruction_name = self.instruction.__class__.__name__   155         if self.debug:   156             print "%8d %s" % (self.pc, instruction_name)   157    158         method = self.get_method(instruction_name)   159    160         # Process any inputs of the instruction.   161    162         self.process_inputs()   163         next_pc = method()   164    165         # Update the program counter.   166    167         if next_pc is None:   168             self.pc += 1   169         else:   170             self.pc = next_pc   171    172     def get_method(self, instruction_name):   173    174         "Return the handler method for the given 'instruction_name'."   175    176         method = getattr(self, instruction_name, None)   177         if method is None:   178             raise IllegalInstruction, (self.pc, instruction_name)   179         return method   180    181     def process_inputs(self):   182    183         """   184         Process any inputs of the current instruction. This permits any directly   185         connected sub-instructions to produce the effects that separate   186         instructions would otherwise have.   187         """   188    189         for input in (self.instruction.input, self.instruction.source):   190             if input is not None:   191                 method = self.get_method(input)   192                 method()   193    194     def jump(self, addr, next):   195    196         """   197         Jump to the subroutine at (or identified by) 'addr'. If 'addr'   198         identifies a library function then invoke the library function and set   199         PC to 'next' afterwards; otherwise, set PC to 'addr'.   200         """   201    202         if isinstance(addr, str):   203             getattr(self, addr)()   204             return next   205         else:   206             self.push_pc(self.pc + 2)   207             return addr   208    209     # Instructions.   210    211     def LoadConst(self):   212         self.value = None, self.operand   213    214     def LoadName(self):   215         frame = self.local_sp_stack[-1]   216         self.value = self.frame_stack[frame + self.operand]   217    218     def StoreName(self):   219         frame = self.local_sp_stack[-1]   220         self.frame_stack[frame + self.operand] = self.value   221    222     LoadTemp = LoadName   223     StoreTemp = StoreName   224    225     def LoadAddress(self):   226         # Preserve context (potentially null).   227         self.value = self.load(self.operand)   228    229     def LoadAddressContext(self):   230         value = self.load(self.operand)   231         # Replace the context with the current value.   232         self.value = self.value[1], value[1]   233    234     def StoreAddress(self):   235         # Preserve context.   236         self.save(self.operand, self.value)   237    238     def MakeObject(self):   239         # Introduce null context for new object.   240         self.value = None, self.new(self.operand)   241    242     def LoadAttr(self):   243         context, ref = self.value   244         # Retrieved context should already be appropriate for the instance.   245         self.value = self.load(ref + self.operand)   246    247     def StoreAttr(self):   248         context, ref = self.value   249         # Target should already be an instance.   250         self.save(ref + self.operand, self.source)   251    252     def LoadAttrIndex(self):   253         context, ref = self.value   254         classcode, attrcode, codeaddr, codedetails = self.load(ref)   255         element = self.objtable[classcode + self.operand]   256         attr_index, class_attr, replace_context, offset = element   257         if attr_index == self.operand:   258             if class_attr:   259                 loaded_context, loaded_ref = self.load(offset) # offset is address of class attribute   260                 if replace_context:   261                     self.value = ref, loaded_ref # classes can also replace the context if compatible   262                     return   263                 self.value = loaded_context, loaded_ref   264             else:   265                 self.value = self.load(ref + offset)   266         else:   267             # NOTE: This should cause an attribute error.   268             raise Exception, "LoadAttrIndex % r" % element   269    270     def StoreAttrIndex(self):   271         context, ref = self.value   272         classcode, attrcode, codeaddr, codedetails = self.load(ref)   273         element = self.objtable[classcode + self.operand]   274         attr_index, class_attr, replace_context, offset = element   275         if attr_index == self.operand:   276             if class_attr:   277                 # NOTE: This should cause an attribute or type error.   278                 # Class attributes cannot be changed at run-time.   279                 raise Exception, "StoreAttrIndex % r" % element   280             else:   281                 self.save(ref + offset, self.source)   282         else:   283             # NOTE: This should cause an attribute error.   284             raise Exception, "StoreAttrIndex % r" % element   285    286     # NOTE: LoadAttrIndexContext is a possibility if a particular attribute can always be overridden.   287    288     def MakeFrame(self):   289         self.invocation_sp_stack.append(len(self.frame_stack))   290         self.frame_stack.extend([None] * self.operand)   291    292     def DropFrame(self):   293         self.local_sp_stack.pop()   294         frame = self.invocation_sp_stack.pop()   295         self.frame_stack = self.frame_stack[:frame] # reset stack before call   296    297     def StoreFrame(self):   298         frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame   299         self.frame_stack[frame + self.operand] = self.value   300    301     def StoreFrameIndex(self):   302         frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame   303         classcode, attrcode, codeaddr, codedetails = self.load(ref)   304         element = self.objtable[classcode + self.operand]   305         attr_index, offset = element   306         if attr_index == self.operand:   307             self.frame_stack[frame + offset] = self.value   308         else:   309             # NOTE: This should cause an argument error.   310             raise Exception, "StoreFrameIndex % r" % element   311    312     def LoadCallable(self):   313         context, ref = self.value   314         classcode, attrcode, codeaddr, codedetails = self.load(ref)   315         self.callable = codeaddr, codedetails   316    317     def StoreCallable(self):   318         context, ref = self.value   319         # NOTE: Should improve the representation and permit direct saving.   320         classcode, attrcode, codeaddr, codedetails = self.load(ref)   321         self.save(ref, (classcode, attrcode) + self.callable)   322    323     def LoadContext(self):   324         context, ref = self.value   325         self.value = None, context   326    327     def CheckFrame(self):   328         operand = self.operand   329         frame = self.invocation_sp_stack[-1]   330         context, ref = self.value   331         classcode, attrcode, codeaddr, codedetails = self.load(ref)   332    333         # Support sliding of the frame to exclude any inappropriate context.   334    335         if context is None:   336             frame = frame[1:]   337             operand -= 1   338         else:   339             if contexttype == self.typetype:   340                 frame = frame[1:]   341                 operand -= 1   342    343         nargs, ndefaults = codedetails   344         if not (nargs - ndefaults <= operand <= nargs):   345             raise Exception, "CheckFrame %r" % (nargs - ndefaults, self.operand, nargs)   346    347         # NOTE: Support population of defaults.   348    349     def CheckSelf(self):   350         context, ref = self.value   351         target_context, target_ref = self.source   352    353         # Load the details of the proposed context and the target's context.   354    355         classcode, attrcode, codeaddr, codedetails = self.load(ref)   356         target_classcode, target_attrcode, target_codeaddr, target_codedetails = self.load(target_context)   357    358         # Find the table entry for the descendant.   359    360         element = self.objtable[target_classcode + attrcode]   361         attr_index, class_attr, replace_context, offset = element   362         if attr_index == attrcode:   363             self.status = 1   364         else:   365             self.status = 0   366    367     def JumpWithFrame(self):   368         self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame   369         return self.jump(self.callable, self.pc + 1) # return to the instruction after this one   370    371     def ExtendFrame(self):   372         frame = self.local_sp_stack[-1]   373         frame.extend([None] * self.operand)   374    375     def Return(self):   376         self.pc = self.pull_pc()   377    378     def LoadResult(self):   379         self.value = self.result   380    381     def StoreResult(self):   382         self.result = self.value   383    384     def Jump(self):   385         return self.operand   386    387     def JumpIfTrue(self):   388         if self.status:   389             return self.operand   390    391     def JumpIfFalse(self):   392         if not self.status:   393             return self.operand   394    395     def LoadException(self):   396         self.value = self.exception   397    398     def StoreException(self):   399         self.exception = self.value   400    401     def RaiseException(self):   402         return self.handler_stack.pop()   403    404     def PushHandler(self):   405         self.handler_stack.append(self.operand)   406    407     def PopHandler(self):   408         self.handler_stack.pop()   409    410     def CheckException(self):   411         self.status = self.value[1] == self.exception   412    413     def TestIdentity(self):   414         self.status = self.value[1] == self.source   415    416     def TestIdentityAddress(self):   417         self.status = self.value[1] == self.operand   418    419     # LoadBoolean is implemented in the generated code.   420     # StoreBoolean is implemented by testing against the True value.   421    422     def InvertBoolean(self):   423         self.status = not self.status   424    425 # vim: tabstop=4 expandtab shiftwidth=4