micropython

Annotated rsvp.py

442:13aae946513b
2011-07-05 Paul Boddie Introduced Instance() in place of None as a result and for the value of the active expression where no definitive object can be deduced. Made all Instance values compare equal to each other in order to avoid duplication in sets. Improved Constant comparisons. Fixed assignment counting where many values are provided in a single assignment. Added inspection support for conditional expressions (since they are used in the standard library).
paul@68 1
#!/usr/bin/env python
paul@68 2
paul@68 3
"""
paul@68 4
A really simple virtual processor employing a simple set of instructions which
paul@68 5
ignore low-level operations and merely concentrate on variable access, structure
paul@68 6
access, structure allocation and function invocations.
paul@68 7
paul@394 8
Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
paul@68 9
paul@68 10
This program is free software; you can redistribute it and/or modify it under
paul@68 11
the terms of the GNU General Public License as published by the Free Software
paul@68 12
Foundation; either version 3 of the License, or (at your option) any later
paul@68 13
version.
paul@68 14
paul@68 15
This program is distributed in the hope that it will be useful, but WITHOUT
paul@68 16
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@68 17
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@68 18
details.
paul@68 19
paul@68 20
You should have received a copy of the GNU General Public License along with
paul@68 21
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@68 22
paul@68 23
--------
paul@68 24
paul@68 25
The execution model of the virtual processor involves the following things:
paul@68 26
paul@145 27
  * Memory                          contains constants, global variable
paul@145 28
                                    references and program code
paul@145 29
paul@145 30
  * PC (program counter) stack      contains the return address associated
paul@145 31
                                    with each function invocation
paul@145 32
paul@145 33
  * Frame stack                     contains invocation frames in use and in
paul@145 34
                                    preparation plus temporary storage
paul@145 35
paul@145 36
  * Local frame pointer stack       refers to the frames in the frame stack
paul@145 37
paul@117 38
  * Invocation frame pointer stack
paul@145 39
paul@117 40
  * Exception handler stack
paul@68 41
paul@145 42
  * Exception handler locals stack  refers to the state of the local frame
paul@145 43
                                    pointer stack
paul@145 44
paul@145 45
  * Exception handler PC stack      refers to the state of the PC stack
paul@68 46
paul@429 47
  * Registers: current context/value,
paul@429 48
               source context/value,
paul@429 49
               current result context/value,
paul@429 50
               current exception value,
paul@145 51
               boolean status value,
paul@145 52
               current callable
paul@68 53
"""
paul@68 54
paul@264 55
from micropython.program import DataValue, ReplaceableContext, PlaceholderContext, FragmentObject
paul@261 56
from rsvplib import Library
paul@182 57
paul@68 58
class IllegalInstruction(Exception):
paul@68 59
    pass
paul@68 60
paul@68 61
class IllegalAddress(Exception):
paul@180 62
    def __init__(self, address):
paul@180 63
        self.address = address
paul@180 64
    def __repr__(self):
paul@180 65
        return "IllegalAddress(%r)" % self.address
paul@180 66
    def __str__(self):
paul@180 67
        return repr(self)
paul@68 68
paul@68 69
class EmptyPCStack(Exception):
paul@68 70
    pass
paul@68 71
paul@92 72
class EmptyFrameStack(Exception):
paul@68 73
    pass
paul@68 74
paul@194 75
class BreakpointReached(Exception):
paul@194 76
    pass
paul@194 77
paul@68 78
class RSVPMachine:
paul@68 79
paul@68 80
    "A really simple virtual processor."
paul@68 81
paul@248 82
    def __init__(self, memory, objlist, paramlist, pc=None, debug=0, abort_upon_exception=0):
paul@68 83
paul@68 84
        """
paul@68 85
        Initialise the processor with a 'memory' (a list of values containing
paul@196 86
        instructions and data), the object and parameter lists 'objlist' and
paul@248 87
        'paramlist', and the optional program counter 'pc'.
paul@68 88
        """
paul@68 89
paul@68 90
        self.memory = memory
paul@202 91
        self._objlist = objlist
paul@202 92
        self._paramlist = paramlist
paul@181 93
        self.objlist = objlist.as_raw()
paul@181 94
        self.paramlist = paramlist.as_raw()
paul@248 95
        self.library = None
paul@196 96
paul@68 97
        self.pc = pc or 0
paul@68 98
        self.debug = debug
paul@246 99
        self.abort_upon_exception = abort_upon_exception
paul@313 100
paul@313 101
        # Profiling.
paul@313 102
paul@389 103
        self.coverage = [0] * len(self.memory)
paul@285 104
        self.counter = 0
paul@313 105
        self.cost = 0
paul@117 106
paul@117 107
        # Stacks.
paul@117 108
paul@68 109
        self.pc_stack = []
paul@68 110
        self.frame_stack = []
paul@143 111
        self.local_sp_stack = [0]
paul@117 112
        self.invocation_sp_stack = []
paul@398 113
paul@398 114
        # Exception handler stacks are used to reset the state of the main
paul@398 115
        # stacks.
paul@398 116
paul@145 117
        self.handler_stack = [len(self.memory) - 1] # final handler is the end of the code
paul@145 118
        self.handler_local_sp_stack = []
paul@398 119
        self.handler_invocation_sp_stack = []
paul@145 120
        self.handler_pc_stack = []
paul@117 121
paul@117 122
        # Registers.
paul@117 123
paul@429 124
        self.value = None
paul@429 125
        self.context = None
paul@429 126
paul@429 127
        self.source_value = None
paul@429 128
        self.source_context = None
paul@429 129
paul@429 130
        self.result_value = None
paul@429 131
        self.result_context = None
paul@429 132
paul@429 133
        self.exception = None
paul@429 134
paul@119 135
        self.instruction = None
paul@117 136
        self.status = None
paul@132 137
        self.callable = None
paul@68 138
paul@146 139
        # Constants.
paul@146 140
paul@221 141
        cls = self._get_class("__builtins__", "AttributeError")
paul@221 142
        self.attr_error = cls.location
paul@221 143
        self.attr_error_instance = cls.instance_template_location
paul@221 144
        cls = self._get_class("__builtins__", "TypeError")
paul@221 145
        self.type_error = cls.location
paul@221 146
        self.type_error_instance = cls.instance_template_location
paul@256 147
        cls = self._get_class("__builtins__", "tuple")
paul@255 148
        self.tuple_class = cls.location
paul@255 149
        self.tuple_instance = cls.instance_template_location
paul@181 150
paul@194 151
        # Debugging attributes.
paul@194 152
paul@194 153
        self.breakpoints = set()
paul@194 154
paul@221 155
    def _get_class(self, module, name):
paul@346 156
        attr = self._objlist.access(module, name)
paul@346 157
        if attr is not None:
paul@346 158
            return attr.get_value()
paul@346 159
        else:
paul@346 160
            return None
paul@221 161
paul@162 162
    # Debugging methods.
paul@162 163
paul@137 164
    def dump(self):
paul@138 165
        print "PC", self.pc, "->", self.load(self.pc)
paul@137 166
        print "PC stack", self.pc_stack
paul@401 167
        print "Frame stack:"
paul@401 168
        if self.local_sp_stack:
paul@401 169
            start = self.local_sp_stack[0]
paul@401 170
            for end in self.local_sp_stack[1:]:
paul@401 171
                print "  %2d" % start, self.frame_stack[start:end]
paul@401 172
                start = end
paul@401 173
            else:
paul@401 174
                print "  %2d" % start, self.frame_stack[start:]
paul@401 175
        print
paul@137 176
        print "Local stack pointers", self.local_sp_stack
paul@137 177
        print "Invocation stack pointers", self.invocation_sp_stack
paul@137 178
        print "Handler stack", self.handler_stack
paul@145 179
        print "Handler frame stack", self.handler_local_sp_stack
paul@145 180
        print "Handler PC stack", self.handler_pc_stack
paul@137 181
        print
paul@429 182
        print "Value", self.value
paul@429 183
        print "Context", self.context
paul@429 184
        print
paul@429 185
        print "Source value", self.source_value
paul@429 186
        print "Source context", self.source_context
paul@429 187
        print
paul@429 188
        print "Result value", self.result_value
paul@429 189
        print "Result context", self.result_context
paul@429 190
        print
paul@429 191
        print "Exception", self.exception
paul@429 192
        print
paul@137 193
        print "Instruction", self.instruction
paul@137 194
        print "Status", self.status
paul@137 195
        print "Callable", self.callable
paul@137 196
paul@399 197
    def show(self, start=None, end=None):
paul@399 198
        self.show_memory(self.memory[start:end], self.coverage[start:end], start or 0)
paul@162 199
paul@162 200
    def show_pc(self, run_in=10):
paul@162 201
        start = max(0, self.pc - run_in)
paul@162 202
        end = self.pc + run_in
paul@162 203
        memory = self.memory[start:end]
paul@389 204
        coverage = self.coverage[start:end]
paul@389 205
        self.show_memory(memory, coverage, start)
paul@162 206
paul@389 207
    def show_memory(self, memory, coverage, start):
paul@389 208
        for i, (c, x) in enumerate(map(None, coverage, memory)):
paul@162 209
            location = start + i
paul@162 210
            if location == self.pc:
paul@162 211
                print "->",
paul@381 212
            elif location in self.pc_stack:
paul@381 213
                print "..",
paul@162 214
            else:
paul@162 215
                print "  ",
paul@389 216
            print "%-5s %5d %r" % (c or "", location, x)
paul@162 217
paul@162 218
    def step(self, dump=0):
paul@138 219
        self.execute()
paul@162 220
        self.show_pc()
paul@162 221
        if dump:
paul@162 222
            self.dump()
paul@162 223
paul@194 224
    def set_break(self, location):
paul@194 225
        self.breakpoints.add(location)
paul@194 226
paul@399 227
    def up(self):
paul@399 228
        retaddr = self.pc_stack[-1]
paul@401 229
        self.set_break(retaddr)
paul@399 230
        self.run()
paul@399 231
paul@162 232
    # Internal operations.
paul@138 233
paul@68 234
    def load(self, address):
paul@68 235
paul@68 236
        "Return the value at the given 'address'."
paul@68 237
paul@68 238
        try:
paul@68 239
            return self.memory[address]
paul@68 240
        except IndexError:
paul@180 241
            raise IllegalAddress(address)
paul@180 242
        except TypeError:
paul@180 243
            raise IllegalAddress(address)
paul@68 244
paul@68 245
    def save(self, address, value):
paul@68 246
paul@68 247
        "Save to the given 'address' the specified 'value'."
paul@68 248
paul@68 249
        try:
paul@68 250
            self.memory[address] = value
paul@68 251
        except IndexError:
paul@180 252
            raise IllegalAddress(address)
paul@180 253
        except TypeError:
paul@180 254
            raise IllegalAddress(address)
paul@68 255
paul@68 256
    def new(self, size):
paul@68 257
paul@68 258
        """
paul@68 259
        Allocate space of the given 'size', returning the address of the space.
paul@68 260
        """
paul@68 261
paul@68 262
        addr = len(self.memory)
paul@68 263
        for i in range(0, size):
paul@68 264
            self.memory.append(None)
paul@68 265
        return addr
paul@68 266
paul@68 267
    def push_pc(self, data):
paul@68 268
paul@68 269
        "Push 'data' onto the PC stack."
paul@68 270
paul@68 271
        self.pc_stack.append(data)
paul@68 272
paul@68 273
    def pull_pc(self):
paul@68 274
paul@68 275
        "Pull a value from the PC stack and return it."
paul@68 276
paul@68 277
        try:
paul@68 278
            return self.pc_stack.pop()
paul@68 279
        except IndexError:
paul@68 280
            raise EmptyPCStack
paul@68 281
paul@90 282
    def run(self):
paul@68 283
paul@68 284
        "Execute code in the memory, starting from the current PC address."
paul@68 285
paul@228 286
        breakpoint = 0
paul@228 287
paul@68 288
        try:
paul@68 289
            while 1:
paul@90 290
                self.execute()
paul@68 291
        except EmptyPCStack:
paul@68 292
            pass
paul@228 293
        except BreakpointReached:
paul@228 294
            breakpoint = 1
paul@68 295
paul@221 296
        print "Execution terminated",
paul@221 297
        if self.exception is not None:
paul@222 298
            ref = self.exception
paul@399 299
            addr = self.load(ref + Library.instance_data_offset)
paul@221 300
            print "with exception:", self.load(ref)
paul@222 301
            print "At address %d: %r" % (addr, self.load(addr))
paul@228 302
        elif breakpoint:
paul@228 303
            print "with breakpoint."
paul@228 304
            print "At address", self.pc
paul@221 305
        else:
paul@221 306
            print "successfully."
paul@313 307
        print "After", self.counter, "instructions at cost", self.cost, "units."
paul@221 308
paul@228 309
    def test(self, module):
paul@228 310
paul@228 311
        """
paul@228 312
        Test the code in the memory by running the code and investigating the
paul@228 313
        contents of variables. Use 'module' to identify result variables.
paul@228 314
        """
paul@228 315
paul@228 316
        self.run()
paul@228 317
        success = 1
paul@228 318
paul@229 319
        if self.exception is None:
paul@229 320
            for name in module.keys():
paul@229 321
                if name.startswith("result"):
paul@229 322
                    label, expected = name.split("_")
paul@229 323
                    attr = module[name]
paul@228 324
paul@229 325
                    # NOTE: Assumptions about headers and content made.
paul@228 326
paul@229 327
                    attr_location = module.location + 1 + attr.position
paul@264 328
                    value = self.load(attr_location)
paul@228 329
paul@264 330
                    if value is not None:
paul@395 331
                        content = self.load(value.ref + Library.instance_data_offset)
paul@243 332
                        print label, expected, content
paul@243 333
                        success = success and (int(expected) == content)
paul@243 334
                    else:
paul@243 335
                        print label, expected, "missing"
paul@243 336
                        success = 0
paul@228 337
paul@229 338
            return success
paul@229 339
        else:
paul@229 340
            return 0
paul@228 341
paul@90 342
    def execute(self):
paul@90 343
paul@90 344
        "Execute code in the memory at the current PC address."
paul@90 345
paul@194 346
        if self.pc in self.breakpoints:
paul@194 347
            self.breakpoints.remove(self.pc)
paul@194 348
            raise BreakpointReached
paul@194 349
paul@119 350
        self.instruction = self.load(self.pc)
paul@118 351
paul@118 352
        # Process any inputs of the instruction.
paul@118 353
paul@119 354
        self.process_inputs()
paul@137 355
paul@137 356
        # Perform the instruction itself.
paul@137 357
paul@137 358
        next_pc = self.perform(self.instruction)
paul@118 359
paul@389 360
        # Update the coverage.
paul@389 361
paul@389 362
        self.coverage[self.pc] += 1
paul@389 363
paul@119 364
        # Update the program counter.
paul@118 365
paul@119 366
        if next_pc is None:
paul@119 367
            self.pc += 1
paul@119 368
        else:
paul@119 369
            self.pc = next_pc
paul@118 370
paul@137 371
    def get_method(self, instruction):
paul@137 372
paul@137 373
        "Return the handler method for the given 'instruction'."
paul@119 374
paul@137 375
        instruction_name = instruction.__class__.__name__
paul@137 376
        if self.debug:
paul@137 377
            print "%8d %s" % (self.pc, instruction_name)
paul@119 378
        method = getattr(self, instruction_name, None)
paul@90 379
        if method is None:
paul@119 380
            raise IllegalInstruction, (self.pc, instruction_name)
paul@118 381
        return method
paul@118 382
paul@313 383
    def perform(self, instruction, is_input=0):
paul@137 384
paul@137 385
        "Perform the 'instruction', returning the next PC value or None."
paul@137 386
paul@313 387
        if not is_input:
paul@313 388
            self.counter += 1
paul@313 389
            self.cost += instruction.cost
paul@430 390
        operand = instruction.get_operand()
paul@137 391
        method = self.get_method(instruction)
paul@430 392
        return method(operand)
paul@137 393
paul@119 394
    def process_inputs(self):
paul@118 395
paul@118 396
        """
paul@119 397
        Process any inputs of the current instruction. This permits any directly
paul@118 398
        connected sub-instructions to produce the effects that separate
paul@118 399
        instructions would otherwise have.
paul@118 400
        """
paul@118 401
paul@429 402
        value, context = self.value, self.context
paul@174 403
        if self.instruction.source is not None:
paul@313 404
            self.perform(self.instruction.source, 1)
paul@429 405
            self.source_value, self.source_context = self.value, self.context
paul@429 406
        self.value, self.context = value, context
paul@174 407
        if self.instruction.input is not None:
paul@313 408
            self.perform(self.instruction.input, 1)
paul@90 409
paul@68 410
    def jump(self, addr, next):
paul@68 411
paul@68 412
        """
paul@68 413
        Jump to the subroutine at (or identified by) 'addr'. If 'addr'
paul@68 414
        identifies a library function then invoke the library function and set
paul@68 415
        PC to 'next' afterwards; otherwise, set PC to 'addr'.
paul@68 416
        """
paul@68 417
paul@181 418
        # Trap library functions introduced through the use of strings instead
paul@181 419
        # of proper locations.
paul@181 420
paul@68 421
        if isinstance(addr, str):
paul@248 422
            handler = self.library and self.library.native_functions[addr](self.library)
paul@196 423
            if handler is None:
paul@196 424
                return next
paul@196 425
            else:
paul@196 426
                return handler
paul@68 427
        else:
paul@138 428
            self.push_pc(self.pc + 1)
paul@119 429
            return addr
paul@68 430
paul@429 431
    # Low-level instructions.
paul@429 432
paul@429 433
    def LoadValue(self, operand):
paul@429 434
        self.value = operand
paul@429 435
paul@429 436
    def LoadContext(self, operand):
paul@429 437
        self.context = operand
paul@429 438
paul@430 439
    def LoadSourceValue(self, operand):
paul@430 440
        self.source_value = operand
paul@430 441
paul@430 442
    def LoadSourceContext(self, operand):
paul@430 443
        self.source_context = operand
paul@430 444
paul@68 445
    # Instructions.
paul@68 446
paul@430 447
    def LoadConst(self, operand):
paul@430 448
        self.LoadContext(operand)
paul@430 449
        self.LoadValue(operand)
paul@223 450
paul@430 451
    def LoadClass(self, operand):
paul@429 452
        self.LoadContext(PlaceholderContext)
paul@430 453
        self.LoadValue(operand)
paul@237 454
paul@430 455
    def LoadFunction(self, operand):
paul@429 456
        self.LoadContext(ReplaceableContext)
paul@430 457
        self.LoadValue(operand)
paul@68 458
paul@430 459
    def LoadName(self, operand):
paul@117 460
        frame = self.local_sp_stack[-1]
paul@430 461
        data = self.frame_stack[frame + operand]
paul@429 462
        self.LoadContext(data.context)
paul@429 463
        self.LoadValue(data.ref)
paul@68 464
paul@430 465
    def StoreName(self, operand):
paul@117 466
        frame = self.local_sp_stack[-1]
paul@430 467
        self.frame_stack[frame + operand] = DataValue(self.source_context, self.source_value)
paul@68 468
paul@72 469
    LoadTemp = LoadName
paul@203 470
paul@430 471
    def StoreTemp(self, operand):
paul@203 472
        frame = self.local_sp_stack[-1]
paul@430 473
        self.frame_stack[frame + operand] = DataValue(self.context, self.value)
paul@72 474
paul@430 475
    def LoadAddress(self, operand):
paul@119 476
        # Preserve context (potentially null).
paul@430 477
        data = self.load(operand)
paul@429 478
        self.LoadContext(data.context)
paul@429 479
        self.LoadValue(data.ref)
paul@72 480
paul@430 481
    def LoadAddressContext(self, operand):
paul@429 482
        # Value becomes context.
paul@430 483
        data = self.load(operand)
paul@429 484
        self.LoadContext(self.value)
paul@429 485
        self.LoadValue(data.ref)
paul@194 486
paul@430 487
    def LoadAddressContextCond(self, operand):
paul@430 488
        data = self.load(operand)
paul@429 489
        data = self._LoadAddressContextCond(data.context, data.ref, self.value)
paul@429 490
        self.LoadContext(data.context)
paul@429 491
        self.LoadValue(data.ref)
paul@72 492
paul@430 493
    def StoreAddress(self, operand):
paul@119 494
        # Preserve context.
paul@430 495
        self.save(operand, DataValue(self.source_context, self.source_value))
paul@72 496
paul@430 497
    def StoreAddressContext(self, operand):
paul@223 498
        # Overwrite context if null.
paul@430 499
        self._StoreAddressContext(operand, self.context, self.value, self.source_context, self.source_value)
paul@223 500
paul@430 501
    def MakeInstance(self, size):
paul@184 502
        # NOTE: Referencing the instance template.
paul@429 503
        addr = self._MakeObject(size, self.value - Library.instance_template_size)
paul@184 504
        # Introduce object as context for the new object.
paul@429 505
        self.LoadContext(addr)
paul@429 506
        self.LoadValue(addr)
paul@72 507
paul@430 508
    def MakeFragment(self, size):
paul@336 509
        # Reserve twice the amount of space.
paul@336 510
        addr = self._MakeFragment(size, size * 2)
paul@246 511
        # NOTE: Context is not relevant for fragments.
paul@429 512
        self.LoadContext(None)
paul@429 513
        self.LoadValue(addr)
paul@246 514
paul@430 515
    def LoadAttr(self, pos):
paul@127 516
        # Retrieved context should already be appropriate for the instance.
paul@184 517
        # NOTE: Adding 1 to skip any header.
paul@430 518
        data = self.load(self.value + pos + 1)
paul@429 519
        self.LoadContext(data.context)
paul@429 520
        self.LoadValue(data.ref)
paul@118 521
paul@430 522
    def StoreAttr(self, pos):
paul@129 523
        # Target should already be an instance.
paul@184 524
        # NOTE: Adding 1 to skip any header.
paul@430 525
        self.save(self.value + pos + 1, DataValue(self.source_context, self.source_value))
paul@119 526
paul@430 527
    def LoadAttrIndex(self, index):
paul@429 528
        data = self.load(self.value)
paul@430 529
        element = self.objlist[data.classcode + index]
paul@238 530
paul@238 531
        if element is not None:
paul@242 532
            attr_index, static_attr, offset = element
paul@430 533
            if attr_index == index:
paul@242 534
                if static_attr:
paul@429 535
                    data = self.load(offset) # offset is address of class/module attribute
paul@238 536
                else:
paul@429 537
                    data = self.load(self.value + offset)
paul@429 538
                self.LoadContext(data.context)
paul@429 539
                self.LoadValue(data.ref)
paul@238 540
                return
paul@238 541
paul@399 542
        self.exception = self._MakeObject(Library.instance_size, self.attr_error_instance)
paul@238 543
        return self.RaiseException()
paul@118 544
paul@226 545
    # LoadAttrIndexContext not defined.
paul@194 546
paul@430 547
    def LoadAttrIndexContextCond(self, index):
paul@429 548
        data = self.load(self.value)
paul@430 549
        element = self.objlist[data.classcode + index]
paul@238 550
paul@238 551
        if element is not None:
paul@242 552
            attr_index, static_attr, offset = element
paul@430 553
            if attr_index == index:
paul@242 554
                if static_attr:
paul@429 555
                    loaded_data = self.load(offset) # offset is address of class/module attribute
paul@429 556
                    # Absent attrcode == class/module.
paul@429 557
                    if data.attrcode is not None:
paul@429 558
                        loaded_data = self._LoadAddressContextCond(loaded_data.context, loaded_data.ref, self.value)
paul@223 559
                else:
paul@429 560
                    loaded_data = self.load(self.value + offset)
paul@429 561
                self.LoadContext(loaded_data.context)
paul@429 562
                self.LoadValue(loaded_data.ref)
paul@238 563
                return
paul@238 564
paul@399 565
        self.exception = self._MakeObject(Library.instance_size, self.attr_error_instance)
paul@238 566
        return self.RaiseException()
paul@194 567
paul@430 568
    def StoreAttrIndex(self, index):
paul@429 569
        data = self.load(self.value)
paul@430 570
        element = self.objlist[data.classcode + index]
paul@238 571
paul@238 572
        if element is not None:
paul@242 573
            attr_index, static_attr, offset = element
paul@430 574
            if attr_index == index:
paul@242 575
                if static_attr:
paul@429 576
                    self._StoreAddressContext(offset, self.context, self.value, self.source_context, self.source_value)
paul@405 577
                    return
paul@238 578
                else:
paul@429 579
                    self.save(self.value + offset, DataValue(self.source_context, self.source_value))
paul@238 580
                    return
paul@238 581
paul@399 582
        self.exception = self._MakeObject(Library.instance_size, self.attr_error_instance)
paul@238 583
        return self.RaiseException()
paul@118 584
paul@127 585
    # NOTE: LoadAttrIndexContext is a possibility if a particular attribute can always be overridden.
paul@127 586
paul@430 587
    def MakeFrame(self, size):
paul@117 588
        self.invocation_sp_stack.append(len(self.frame_stack))
paul@430 589
        self.frame_stack.extend([None] * size)
paul@98 590
paul@430 591
    def DropFrame(self, operand=None):
paul@117 592
        self.local_sp_stack.pop()
paul@117 593
        frame = self.invocation_sp_stack.pop()
paul@248 594
        del self.frame_stack[frame:] # reset stack before call
paul@98 595
paul@430 596
    def StoreFrame(self, pos):
paul@129 597
        frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame
paul@430 598
        self.frame_stack[frame + pos] = DataValue(self.context, self.value)
paul@129 599
paul@430 600
    def StoreFrameIndex(self, index):
paul@129 601
        frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame
paul@429 602
        data = self.load(self.value)
paul@430 603
        element = self.paramlist[data.funccode + index]
paul@238 604
paul@238 605
        if element is not None:
paul@238 606
            # NOTE: Need to ensure correct positioning where a context has been generated.
paul@238 607
            param_index, offset = element
paul@430 608
            if param_index == index:
paul@429 609
                self.frame_stack[frame + offset] = DataValue(self.source_context, self.source_value)
paul@238 610
                return
paul@238 611
paul@399 612
        self.exception = self._MakeObject(Library.instance_size, self.type_error_instance)
paul@238 613
        return self.RaiseException()
paul@129 614
paul@430 615
    def LoadCallable(self, operand=None):
paul@429 616
        data = self.load(self.value)
paul@219 617
        self.callable = data.codeaddr
paul@132 618
paul@430 619
    def StoreCallable(self, operand=None):
paul@134 620
        # NOTE: Should improve the representation and permit direct saving.
paul@429 621
        data = self.load(self.value)
paul@429 622
        self.save(self.value, data.with_callable(self.callable))
paul@132 623
paul@430 624
    def LoadContextIntoValue(self, operand=None):
paul@218 625
        # NOTE: Omission of the context of the context would make things like
paul@218 626
        # NOTE: self() inside methods impossible.
paul@429 627
        self.LoadValue(self.context)
paul@132 628
paul@430 629
    def CheckContext(self, operand=None):
paul@429 630
        self.status = self.value is not ReplaceableContext
paul@222 631
paul@430 632
    def CheckClass(self, operand=None):
paul@429 633
        if self.value in (ReplaceableContext, PlaceholderContext):
paul@237 634
            self.status = 0
paul@237 635
            return
paul@237 636
paul@429 637
        data = self.load(self.value)
paul@222 638
paul@222 639
        # Classes are not themselves usable as the self argument.
paul@222 640
        # NOTE: This may change at some point.
paul@222 641
        # However, where classes appear as the context, instance
paul@222 642
        # compatibility is required in the first argument.
paul@222 643
paul@234 644
        self.status = data.attrcode is None # absent attrcode == class
paul@222 645
paul@430 646
    def CheckFrame(self, operand):
paul@430 647
        (nargs, ndefaults) = operand
paul@215 648
paul@215 649
        # The frame is actually installed as the locals.
paul@215 650
        # Retrieve the context from the first local.
paul@215 651
paul@215 652
        frame = self.local_sp_stack[-1]
paul@215 653
        nlocals = len(self.frame_stack[frame:])
paul@134 654
paul@234 655
        if not ((nargs - ndefaults) <= nlocals):
paul@430 656
            raise Exception, "CheckFrame %r (%r <= %r <= %r)" % (operand, nargs - ndefaults, nlocals, nargs)
paul@399 657
            self.exception = self._MakeObject(Library.instance_size, self.type_error_instance)
paul@221 658
            return self.RaiseException()
paul@215 659
paul@430 660
    def CheckExtra(self, nargs):
paul@256 661
paul@256 662
        # The frame is actually installed as the locals.
paul@256 663
        # Retrieve the context from the first local.
paul@256 664
paul@256 665
        frame = self.local_sp_stack[-1]
paul@256 666
        nlocals = len(self.frame_stack[frame:])
paul@256 667
paul@256 668
        # Provide the extra star parameter if necessary.
paul@256 669
paul@256 670
        if nlocals == nargs:
paul@256 671
            self.frame_stack.extend([None]) # ExtendFrame(1)
paul@256 672
paul@430 673
    def FillDefaults(self, operand):
paul@430 674
        (nargs, ndefaults) = operand
paul@215 675
paul@215 676
        # The frame is actually installed as the locals.
paul@215 677
paul@215 678
        frame = self.local_sp_stack[-1]
paul@215 679
        nlocals = len(self.frame_stack[frame:])
paul@134 680
paul@190 681
        # Support population of defaults.
paul@190 682
        # This involves copying the "attributes" of a function into the frame.
paul@190 683
paul@215 684
        default = nlocals - (nargs - ndefaults)
paul@215 685
        self.frame_stack.extend([None] * (nargs - nlocals))
paul@215 686
        pos = nlocals
paul@190 687
paul@215 688
        while pos < nargs:
paul@429 689
            self.frame_stack[frame + pos] = self.load(self.value + default + 1) # skip header
paul@190 690
            default += 1
paul@190 691
            pos += 1
paul@117 692
paul@430 693
    def CopyExtra(self, start):
paul@255 694
paul@255 695
        # The frame is the source of the extra arguments.
paul@255 696
paul@255 697
        frame = self.local_sp_stack[-1]
paul@255 698
        nlocals = len(self.frame_stack[frame:])
paul@255 699
paul@255 700
        # Make a tuple to hold the arguments.
paul@255 701
paul@255 702
        ref = self._MakeObject(nlocals - start + 1, self.tuple_instance)
paul@255 703
paul@255 704
        extra = 0
paul@255 705
        pos = start
paul@255 706
paul@255 707
        while pos < nlocals:
paul@255 708
            self.save(ref + extra + 1, self.frame_stack[frame + pos]) # skip header when storing
paul@255 709
            extra += 1
paul@255 710
            pos += 1
paul@255 711
paul@429 712
        self.LoadContext(ref)
paul@429 713
        self.LoadValue(ref)
paul@255 714
paul@430 715
    def CheckInstance(self, operand=None):
paul@285 716
        # For the 'self' parameter in an invoked function, the proposed context
paul@285 717
        # ('self') is checked against the target's context.
paul@429 718
        self.status = self._CheckInstance(self.value, self.source_value)
paul@308 719
paul@430 720
    def JumpInFrame(self, operand=None):
paul@230 721
        codeaddr = self.callable
paul@230 722
        return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one
paul@230 723
paul@430 724
    def JumpWithFrame(self, operand=None):
paul@219 725
        codeaddr = self.callable
paul@132 726
        self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame
paul@138 727
        return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one
paul@98 728
paul@430 729
    def JumpWithFrameDirect(self, addr):
paul@215 730
        self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame
paul@430 731
        return self.jump(addr, self.pc + 1) # return to the instruction after this one
paul@215 732
paul@430 733
    def ExtendFrame(self, size):
paul@430 734
        self.frame_stack.extend([None] * size)
paul@98 735
paul@430 736
    def AdjustFrame(self, size):
paul@430 737
        self.invocation_sp_stack[-1] += size
paul@137 738
paul@430 739
    def Return(self, operand=None):
paul@138 740
        return self.pull_pc()
paul@68 741
paul@430 742
    def LoadResultIntoValue(self, operand=None):
paul@429 743
        self.LoadContext(self.result_context)
paul@429 744
        self.LoadValue(self.result_value)
paul@117 745
paul@430 746
    def LoadValueIntoResult(self, operand=None):
paul@429 747
        self.result_context = self.context
paul@429 748
        self.result_value = self.value
paul@117 749
paul@430 750
    def Jump(self, addr):
paul@430 751
        return addr
paul@98 752
paul@430 753
    def JumpIfTrue(self, addr):
paul@117 754
        if self.status:
paul@430 755
            return addr
paul@68 756
paul@430 757
    def JumpIfFalse(self, addr):
paul@117 758
        if not self.status:
paul@430 759
            return addr
paul@68 760
paul@430 761
    def LoadException(self, operand=None):
paul@429 762
        self.LoadContext(self.exception)
paul@429 763
        self.LoadValue(self.exception)
paul@129 764
paul@430 765
    def StoreException(self, operand=None):
paul@429 766
        self.exception = self.value
paul@129 767
paul@430 768
    def ClearException(self, operand=None):
paul@232 769
        self.exception = None
paul@232 770
paul@430 771
    def RaiseException(self, operand=None):
paul@399 772
        # NOTE: Adding the program counter as the first attribute after __class__.
paul@401 773
        self.save(self.exception + Library.instance_data_offset, self.pc)
paul@221 774
        # Jumping to the current handler.
paul@246 775
        if self.abort_upon_exception:
paul@246 776
            raise Exception
paul@145 777
        return self.handler_stack[-1]
paul@129 778
paul@430 779
    def PushHandler(self, addr):
paul@430 780
        self.handler_stack.append(addr)
paul@145 781
        self.handler_local_sp_stack.append(len(self.local_sp_stack))
paul@398 782
        self.handler_invocation_sp_stack.append(len(self.invocation_sp_stack))
paul@145 783
        self.handler_pc_stack.append(len(self.pc_stack))
paul@129 784
paul@430 785
    def PopHandler(self, nframes):
paul@398 786
        # Get the new local frame pointer and PC stack references.
paul@398 787
        local_sp_top = self.handler_local_sp_stack[-nframes]
paul@398 788
        invocation_sp_top = self.handler_invocation_sp_stack[-nframes]
paul@398 789
        pc_top = self.handler_pc_stack[-nframes]
paul@145 790
        # Reduce the local frame pointer stack to refer to the handler's frame.
paul@398 791
        del self.local_sp_stack[local_sp_top:]
paul@398 792
        # Reduce the invocation frame pointer stack to refer to an outer frame.
paul@398 793
        del self.invocation_sp_stack[invocation_sp_top:]
paul@145 794
        # Reduce the PC stack to discard all superfluous return addresses.
paul@398 795
        del self.pc_stack[pc_top:]
paul@398 796
        # Remove elements from the handler stacks.
paul@398 797
        del self.handler_pc_stack[-nframes:]
paul@398 798
        del self.handler_local_sp_stack[-nframes:]
paul@398 799
        del self.handler_invocation_sp_stack[-nframes:]
paul@398 800
        del self.handler_stack[-nframes:]
paul@129 801
paul@430 802
    def CheckException(self, operand=None):
paul@429 803
        self.status = self.exception is not None and self._CheckInstance(self.exception, self.value)
paul@129 804
paul@430 805
    def TestIdentity(self, operand=None):
paul@429 806
        self.status = self.value == self.source_value
paul@129 807
paul@430 808
    def TestIdentityAddress(self, addr):
paul@430 809
        self.status = self.value == addr
paul@129 810
paul@129 811
    # LoadBoolean is implemented in the generated code.
paul@129 812
    # StoreBoolean is implemented by testing against the True value.
paul@68 813
paul@430 814
    def InvertBoolean(self, operand=None):
paul@132 815
        self.status = not self.status
paul@132 816
paul@145 817
    # Common implementation details.
paul@145 818
paul@145 819
    def _CheckInstance(self, ref, cls):
paul@182 820
        data = self.load(ref)
paul@182 821
        target_data = self.load(cls)
paul@145 822
paul@223 823
        # Insist on instance vs. class.
paul@223 824
paul@242 825
        if data.attrcode is None: # absent attrcode == class/module
paul@223 826
            return 0
paul@191 827
paul@207 828
        if target_data.attrcode is not None: # present attrcode == instance
paul@191 829
            return 0
paul@191 830
paul@145 831
        # Find the table entry for the descendant.
paul@145 832
paul@182 833
        element = self.objlist[target_data.classcode + data.attrcode]
paul@238 834
paul@220 835
        if element is not None:
paul@242 836
            attr_index, static_attr, offset = element
paul@220 837
            return attr_index == data.attrcode
paul@220 838
        else:
paul@220 839
            return 0
paul@181 840
paul@181 841
    def _MakeObject(self, size, ref):
paul@203 842
        # Load the template.
paul@182 843
        data = self.load(ref)
paul@181 844
        addr = self.new(size)
paul@203 845
        # Save the header, overriding the size.
paul@203 846
        self.save(addr, data.with_size(size))
paul@181 847
        return addr
paul@181 848
paul@336 849
    def _MakeFragment(self, occupied, size):
paul@336 850
        addr = self.new(size)
paul@246 851
        # Save the header, overriding the size.
paul@336 852
        self.save(addr, FragmentObject(occupied, size))
paul@246 853
        return addr
paul@246 854
paul@429 855
    def _LoadAddressContextCond(self, context, value, inst_value):
paul@191 856
        # Check the instance context against the target's context.
paul@230 857
        # This provides the context overriding for methods.
paul@429 858
        if context is ReplaceableContext or context is not PlaceholderContext and self._CheckInstance(inst_value, context):
paul@191 859
            # Replace the context with the instance.
paul@429 860
            return DataValue(inst_value, value)
paul@191 861
        else:
paul@429 862
            return DataValue(context, value)
paul@191 863
paul@429 864
    def _StoreAddressContext(self, location, context, value, source_context, source_value):
paul@429 865
        if source_context is ReplaceableContext:
paul@429 866
            context = value
paul@405 867
        else:
paul@429 868
            context = source_context
paul@429 869
        self.save(location, DataValue(context, source_value))
paul@405 870
paul@228 871
# Convenience functions.
paul@228 872
paul@246 873
def machine(program, with_builtins=0, debug=0, abort_upon_exception=0):
paul@228 874
    print "Making the image..."
paul@228 875
    code = program.get_image(with_builtins)
paul@228 876
    print "Getting raw structures..."
paul@228 877
    ot = program.get_object_table()
paul@228 878
    pt = program.get_parameter_table()
paul@228 879
    objlist = ot.as_list()
paul@228 880
    paramlist = pt.as_list()
paul@228 881
    print "Getting raw image..."
paul@228 882
    rc = program.get_raw_image()
paul@228 883
    print "Initialising the machine..."
paul@228 884
    importer = program.get_importer()
paul@354 885
    constants = {}
paul@354 886
    for x in (True, False, NotImplemented):
paul@354 887
        constants[x] = importer.get_constant(x).location
paul@248 888
    rm = RSVPMachine(rc, objlist, paramlist, debug=debug, abort_upon_exception=abort_upon_exception)
paul@354 889
    library = Library(rm, constants)
paul@248 890
    rm.library = library
paul@228 891
    rm.pc = program.code_location
paul@241 892
    print "Returning program occupying %d locations." % len(rm.memory)
paul@228 893
    return rm
paul@228 894
paul@68 895
# vim: tabstop=4 expandtab shiftwidth=4